@guanghechen/eventbus 7.0.1
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 +21 -0
- package/README.md +147 -0
- package/lib/cjs/index.cjs +153 -0
- package/lib/esm/index.mjs +151 -0
- package/lib/types/index.d.ts +134 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present guanghechen (https://github.com/guanghechen)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<h1 align="center">
|
|
3
|
+
<a href="https://github.com/guanghechen/sora/tree/@guanghechen/eventbus@7.0.0/packages/eventbus#readme">@guanghechen/eventbus</a>
|
|
4
|
+
</h1>
|
|
5
|
+
<div align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@guanghechen/eventbus">
|
|
7
|
+
<img
|
|
8
|
+
alt="Npm Version"
|
|
9
|
+
src="https://img.shields.io/npm/v/@guanghechen/eventbus.svg"
|
|
10
|
+
/>
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@guanghechen/eventbus">
|
|
13
|
+
<img
|
|
14
|
+
alt="Npm Download"
|
|
15
|
+
src="https://img.shields.io/npm/dm/@guanghechen/eventbus.svg"
|
|
16
|
+
/>
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@guanghechen/eventbus">
|
|
19
|
+
<img
|
|
20
|
+
alt="Npm License"
|
|
21
|
+
src="https://img.shields.io/npm/l/@guanghechen/eventbus.svg"
|
|
22
|
+
/>
|
|
23
|
+
</a>
|
|
24
|
+
<a href="#install">
|
|
25
|
+
<img
|
|
26
|
+
alt="Module Formats: cjs, esm"
|
|
27
|
+
src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
|
|
28
|
+
/>
|
|
29
|
+
</a>
|
|
30
|
+
<a href="https://github.com/nodejs/node">
|
|
31
|
+
<img
|
|
32
|
+
alt="Node.js Version"
|
|
33
|
+
src="https://img.shields.io/node/v/@guanghechen/eventbus"
|
|
34
|
+
/>
|
|
35
|
+
</a>
|
|
36
|
+
<a href="https://github.com/vitest-dev/vitest">
|
|
37
|
+
<img
|
|
38
|
+
alt="Tested with Vitest"
|
|
39
|
+
src="https://img.shields.io/badge/tested_with-vitest-6e9f18.svg"
|
|
40
|
+
/>
|
|
41
|
+
</a>
|
|
42
|
+
<a href="https://github.com/prettier/prettier">
|
|
43
|
+
<img
|
|
44
|
+
alt="Code Style: prettier"
|
|
45
|
+
src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
|
|
46
|
+
/>
|
|
47
|
+
</a>
|
|
48
|
+
</div>
|
|
49
|
+
</header>
|
|
50
|
+
<br/>
|
|
51
|
+
|
|
52
|
+
A simple event bus with disposable support.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- Type-specific listeners (`on`/`once`) and global subscribers (`subscribe`)
|
|
57
|
+
- Returns `IUnsubscribable` for easy cleanup
|
|
58
|
+
- Implements `IBatchDisposable` for resource management
|
|
59
|
+
- Exception isolation: one handler's error won't stop other handlers
|
|
60
|
+
- Alias methods: `off` for `removeListener`, `emit` for `dispatch`
|
|
61
|
+
- `listenerCount()` to get the number of listeners
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
- npm
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install --save @guanghechen/eventbus
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- yarn
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
yarn add @guanghechen/eventbus
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import type { IEvent, IEventHandler } from '@guanghechen/eventbus'
|
|
81
|
+
import { EventBus } from '@guanghechen/eventbus'
|
|
82
|
+
|
|
83
|
+
enum EventTypes {
|
|
84
|
+
INIT = 'INIT',
|
|
85
|
+
EXIT = 'EXIT',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const eventBus = new EventBus<EventTypes>('my-bus')
|
|
89
|
+
|
|
90
|
+
const handle: IEventHandler<EventTypes> = (evt, eventBus): void => {
|
|
91
|
+
console.log('evt:', evt)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Listen for specific event, returns IUnsubscribable
|
|
95
|
+
const unsub1 = eventBus.on(EventTypes.INIT, handle)
|
|
96
|
+
|
|
97
|
+
// Listen for specific event, auto-removed after first call
|
|
98
|
+
const unsub2 = eventBus.once(EventTypes.INIT, handle)
|
|
99
|
+
|
|
100
|
+
// Subscribe to all events
|
|
101
|
+
const unsub3 = eventBus.subscribe(handle)
|
|
102
|
+
|
|
103
|
+
// Dispatch an event
|
|
104
|
+
eventBus.dispatch({ type: EventTypes.INIT, payload: { id: 1 } })
|
|
105
|
+
// Or use alias
|
|
106
|
+
eventBus.emit({ type: EventTypes.INIT, payload: { id: 1 } })
|
|
107
|
+
|
|
108
|
+
// Remove listener
|
|
109
|
+
eventBus.off(EventTypes.INIT, handle)
|
|
110
|
+
// Or use unsubscribable
|
|
111
|
+
unsub1.unsubscribe()
|
|
112
|
+
|
|
113
|
+
// Get listener count
|
|
114
|
+
console.log(eventBus.listenerCount()) // all listeners + subscribers
|
|
115
|
+
console.log(eventBus.listenerCount(EventTypes.INIT)) // only INIT listeners
|
|
116
|
+
|
|
117
|
+
// Cleanup all listeners and subscribers
|
|
118
|
+
eventBus.cleanup()
|
|
119
|
+
|
|
120
|
+
// Dispose the event bus (also disposes registered disposables)
|
|
121
|
+
eventBus.dispose()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
### `EventBus<T>`
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
| ----------------------------------- | ----------------------------------------------------- |
|
|
130
|
+
| `on(type, handle)` | Listen for a specific event type |
|
|
131
|
+
| `once(type, handle)` | Listen for a specific event type, auto-removed |
|
|
132
|
+
| `off(type, handle)` | Remove a listener (alias of `removeListener`) |
|
|
133
|
+
| `removeListener(type, handle)` | Remove a listener |
|
|
134
|
+
| `subscribe(handle, once?)` | Subscribe to all events |
|
|
135
|
+
| `unsubscribe(handle)` | Cancel a subscription |
|
|
136
|
+
| `dispatch(evt)` | Dispatch an event |
|
|
137
|
+
| `emit(evt)` | Dispatch an event (alias of `dispatch`) |
|
|
138
|
+
| `listenerCount(type?)` | Get listener count |
|
|
139
|
+
| `cleanup()` | Remove all listeners and subscribers |
|
|
140
|
+
| `dispose()` | Dispose the event bus |
|
|
141
|
+
| `registerDisposable(disposable)` | Register a disposable to be disposed with the bus |
|
|
142
|
+
|
|
143
|
+
## Reference
|
|
144
|
+
|
|
145
|
+
- [homepage][homepage]
|
|
146
|
+
|
|
147
|
+
[homepage]: https://github.com/guanghechen/sora/tree/@guanghechen/eventbus@7.0.0/packages/eventbus#readme
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function filterInPlace(elements, predicate) {
|
|
4
|
+
let tot = 0;
|
|
5
|
+
for (let i = 0; i < elements.length; ++i) {
|
|
6
|
+
const el = elements[i];
|
|
7
|
+
if (predicate(el, i)) {
|
|
8
|
+
elements[tot] = el;
|
|
9
|
+
tot += 1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
elements.length = tot;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class EventBus {
|
|
16
|
+
name;
|
|
17
|
+
_disposed;
|
|
18
|
+
_listeners;
|
|
19
|
+
_subscribers;
|
|
20
|
+
_disposables;
|
|
21
|
+
constructor(name) {
|
|
22
|
+
this.name = name;
|
|
23
|
+
this._disposed = false;
|
|
24
|
+
this._listeners = new Map();
|
|
25
|
+
this._subscribers = [];
|
|
26
|
+
this._disposables = [];
|
|
27
|
+
}
|
|
28
|
+
get disposed() {
|
|
29
|
+
return this._disposed;
|
|
30
|
+
}
|
|
31
|
+
dispose() {
|
|
32
|
+
if (this._disposed)
|
|
33
|
+
return;
|
|
34
|
+
this._disposed = true;
|
|
35
|
+
const errors = [];
|
|
36
|
+
for (const disposable of this._disposables) {
|
|
37
|
+
try {
|
|
38
|
+
disposable.dispose();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
errors.push(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
this._disposables.length = 0;
|
|
45
|
+
this._listeners.clear();
|
|
46
|
+
this._subscribers.length = 0;
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
throw new AggregateError(errors, 'EventBus dispose encountered errors');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
registerDisposable(disposable) {
|
|
52
|
+
if (disposable.disposed)
|
|
53
|
+
return;
|
|
54
|
+
if (this._disposed) {
|
|
55
|
+
disposable.dispose();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this._disposables.push(disposable);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
on(type, handle) {
|
|
62
|
+
return this._addListener(type, handle, false);
|
|
63
|
+
}
|
|
64
|
+
once(type, handle) {
|
|
65
|
+
return this._addListener(type, handle, true);
|
|
66
|
+
}
|
|
67
|
+
off(type, handle) {
|
|
68
|
+
this.removeListener(type, handle);
|
|
69
|
+
}
|
|
70
|
+
removeListener(type, handle) {
|
|
71
|
+
const listeners = this._listeners.get(type);
|
|
72
|
+
if (listeners) {
|
|
73
|
+
filterInPlace(listeners, listener => listener.handle !== handle);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
subscribe(handle, once = false) {
|
|
77
|
+
const existing = this._subscribers.find(subscriber => subscriber.handle === handle);
|
|
78
|
+
if (existing) {
|
|
79
|
+
existing.once = once;
|
|
80
|
+
return { unsubscribe: () => this.unsubscribe(handle) };
|
|
81
|
+
}
|
|
82
|
+
this._subscribers.push({ once, handle: handle });
|
|
83
|
+
return { unsubscribe: () => this.unsubscribe(handle) };
|
|
84
|
+
}
|
|
85
|
+
unsubscribe(handle) {
|
|
86
|
+
filterInPlace(this._subscribers, subscriber => subscriber.handle !== handle);
|
|
87
|
+
}
|
|
88
|
+
dispatch(evt) {
|
|
89
|
+
if (this._disposed)
|
|
90
|
+
return;
|
|
91
|
+
const errors = [];
|
|
92
|
+
const subscribers = [...this._subscribers];
|
|
93
|
+
for (const subscriber of subscribers) {
|
|
94
|
+
try {
|
|
95
|
+
subscriber.handle(evt, this);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
errors.push(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
filterInPlace(this._subscribers, subscriber => !subscriber.once);
|
|
102
|
+
const listeners = this._listeners.get(evt.type);
|
|
103
|
+
if (listeners) {
|
|
104
|
+
const snapshot = [...listeners];
|
|
105
|
+
for (const listener of snapshot) {
|
|
106
|
+
try {
|
|
107
|
+
listener.handle(evt, this);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
errors.push(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
filterInPlace(listeners, listener => !listener.once);
|
|
114
|
+
}
|
|
115
|
+
if (errors.length > 0) {
|
|
116
|
+
throw new AggregateError(errors, 'EventBus dispatch encountered errors');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
emit(evt) {
|
|
120
|
+
this.dispatch(evt);
|
|
121
|
+
}
|
|
122
|
+
listenerCount(type) {
|
|
123
|
+
if (type !== undefined) {
|
|
124
|
+
const listeners = this._listeners.get(type);
|
|
125
|
+
return listeners ? listeners.length : 0;
|
|
126
|
+
}
|
|
127
|
+
let count = this._subscribers.length;
|
|
128
|
+
for (const listeners of this._listeners.values()) {
|
|
129
|
+
count += listeners.length;
|
|
130
|
+
}
|
|
131
|
+
return count;
|
|
132
|
+
}
|
|
133
|
+
cleanup() {
|
|
134
|
+
this._listeners.clear();
|
|
135
|
+
this._subscribers.length = 0;
|
|
136
|
+
}
|
|
137
|
+
_addListener(type, handle, once) {
|
|
138
|
+
let listeners = this._listeners.get(type);
|
|
139
|
+
if (!listeners) {
|
|
140
|
+
listeners = [];
|
|
141
|
+
this._listeners.set(type, listeners);
|
|
142
|
+
}
|
|
143
|
+
const existing = listeners.find(listener => listener.handle === handle);
|
|
144
|
+
if (existing) {
|
|
145
|
+
existing.once = once;
|
|
146
|
+
return { unsubscribe: () => this.removeListener(type, handle) };
|
|
147
|
+
}
|
|
148
|
+
listeners.push({ once, handle: handle });
|
|
149
|
+
return { unsubscribe: () => this.removeListener(type, handle) };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
exports.EventBus = EventBus;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
function filterInPlace(elements, predicate) {
|
|
2
|
+
let tot = 0;
|
|
3
|
+
for (let i = 0; i < elements.length; ++i) {
|
|
4
|
+
const el = elements[i];
|
|
5
|
+
if (predicate(el, i)) {
|
|
6
|
+
elements[tot] = el;
|
|
7
|
+
tot += 1;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
elements.length = tot;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class EventBus {
|
|
14
|
+
name;
|
|
15
|
+
_disposed;
|
|
16
|
+
_listeners;
|
|
17
|
+
_subscribers;
|
|
18
|
+
_disposables;
|
|
19
|
+
constructor(name) {
|
|
20
|
+
this.name = name;
|
|
21
|
+
this._disposed = false;
|
|
22
|
+
this._listeners = new Map();
|
|
23
|
+
this._subscribers = [];
|
|
24
|
+
this._disposables = [];
|
|
25
|
+
}
|
|
26
|
+
get disposed() {
|
|
27
|
+
return this._disposed;
|
|
28
|
+
}
|
|
29
|
+
dispose() {
|
|
30
|
+
if (this._disposed)
|
|
31
|
+
return;
|
|
32
|
+
this._disposed = true;
|
|
33
|
+
const errors = [];
|
|
34
|
+
for (const disposable of this._disposables) {
|
|
35
|
+
try {
|
|
36
|
+
disposable.dispose();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
errors.push(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this._disposables.length = 0;
|
|
43
|
+
this._listeners.clear();
|
|
44
|
+
this._subscribers.length = 0;
|
|
45
|
+
if (errors.length > 0) {
|
|
46
|
+
throw new AggregateError(errors, 'EventBus dispose encountered errors');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
registerDisposable(disposable) {
|
|
50
|
+
if (disposable.disposed)
|
|
51
|
+
return;
|
|
52
|
+
if (this._disposed) {
|
|
53
|
+
disposable.dispose();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this._disposables.push(disposable);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
on(type, handle) {
|
|
60
|
+
return this._addListener(type, handle, false);
|
|
61
|
+
}
|
|
62
|
+
once(type, handle) {
|
|
63
|
+
return this._addListener(type, handle, true);
|
|
64
|
+
}
|
|
65
|
+
off(type, handle) {
|
|
66
|
+
this.removeListener(type, handle);
|
|
67
|
+
}
|
|
68
|
+
removeListener(type, handle) {
|
|
69
|
+
const listeners = this._listeners.get(type);
|
|
70
|
+
if (listeners) {
|
|
71
|
+
filterInPlace(listeners, listener => listener.handle !== handle);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
subscribe(handle, once = false) {
|
|
75
|
+
const existing = this._subscribers.find(subscriber => subscriber.handle === handle);
|
|
76
|
+
if (existing) {
|
|
77
|
+
existing.once = once;
|
|
78
|
+
return { unsubscribe: () => this.unsubscribe(handle) };
|
|
79
|
+
}
|
|
80
|
+
this._subscribers.push({ once, handle: handle });
|
|
81
|
+
return { unsubscribe: () => this.unsubscribe(handle) };
|
|
82
|
+
}
|
|
83
|
+
unsubscribe(handle) {
|
|
84
|
+
filterInPlace(this._subscribers, subscriber => subscriber.handle !== handle);
|
|
85
|
+
}
|
|
86
|
+
dispatch(evt) {
|
|
87
|
+
if (this._disposed)
|
|
88
|
+
return;
|
|
89
|
+
const errors = [];
|
|
90
|
+
const subscribers = [...this._subscribers];
|
|
91
|
+
for (const subscriber of subscribers) {
|
|
92
|
+
try {
|
|
93
|
+
subscriber.handle(evt, this);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
errors.push(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
filterInPlace(this._subscribers, subscriber => !subscriber.once);
|
|
100
|
+
const listeners = this._listeners.get(evt.type);
|
|
101
|
+
if (listeners) {
|
|
102
|
+
const snapshot = [...listeners];
|
|
103
|
+
for (const listener of snapshot) {
|
|
104
|
+
try {
|
|
105
|
+
listener.handle(evt, this);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
errors.push(error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
filterInPlace(listeners, listener => !listener.once);
|
|
112
|
+
}
|
|
113
|
+
if (errors.length > 0) {
|
|
114
|
+
throw new AggregateError(errors, 'EventBus dispatch encountered errors');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
emit(evt) {
|
|
118
|
+
this.dispatch(evt);
|
|
119
|
+
}
|
|
120
|
+
listenerCount(type) {
|
|
121
|
+
if (type !== undefined) {
|
|
122
|
+
const listeners = this._listeners.get(type);
|
|
123
|
+
return listeners ? listeners.length : 0;
|
|
124
|
+
}
|
|
125
|
+
let count = this._subscribers.length;
|
|
126
|
+
for (const listeners of this._listeners.values()) {
|
|
127
|
+
count += listeners.length;
|
|
128
|
+
}
|
|
129
|
+
return count;
|
|
130
|
+
}
|
|
131
|
+
cleanup() {
|
|
132
|
+
this._listeners.clear();
|
|
133
|
+
this._subscribers.length = 0;
|
|
134
|
+
}
|
|
135
|
+
_addListener(type, handle, once) {
|
|
136
|
+
let listeners = this._listeners.get(type);
|
|
137
|
+
if (!listeners) {
|
|
138
|
+
listeners = [];
|
|
139
|
+
this._listeners.set(type, listeners);
|
|
140
|
+
}
|
|
141
|
+
const existing = listeners.find(listener => listener.handle === handle);
|
|
142
|
+
if (existing) {
|
|
143
|
+
existing.once = once;
|
|
144
|
+
return { unsubscribe: () => this.removeListener(type, handle) };
|
|
145
|
+
}
|
|
146
|
+
listeners.push({ once, handle: handle });
|
|
147
|
+
return { unsubscribe: () => this.removeListener(type, handle) };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { EventBus };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { IBatchDisposable, IUnsubscribable, IDisposable } from '@guanghechen/types';
|
|
2
|
+
|
|
3
|
+
type IEventType = number | string | symbol;
|
|
4
|
+
type IEventPayload = number | boolean | string | symbol | object;
|
|
5
|
+
interface IEvent<T extends IEventType = IEventType, P extends IEventPayload = IEventPayload> {
|
|
6
|
+
type: T;
|
|
7
|
+
payload?: P;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The returned value will be ignored.
|
|
11
|
+
* @param evt
|
|
12
|
+
* @param eventBus
|
|
13
|
+
*/
|
|
14
|
+
type IEventHandler<T extends IEventType = IEventType, P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>> = (evt: Readonly<E>, eventBus: IEventBus<T>) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Internal subscription record.
|
|
17
|
+
*/
|
|
18
|
+
interface IEventSubscription<T extends IEventType = IEventType, P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>> {
|
|
19
|
+
/**
|
|
20
|
+
* If true, will be auto-removed after first called.
|
|
21
|
+
*/
|
|
22
|
+
once: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Event handler.
|
|
25
|
+
*/
|
|
26
|
+
handle: IEventHandler<T, P, E>;
|
|
27
|
+
}
|
|
28
|
+
interface IEventBus<T extends IEventType> extends IBatchDisposable {
|
|
29
|
+
/**
|
|
30
|
+
* Name of the event bus instance, useful for debugging.
|
|
31
|
+
*/
|
|
32
|
+
readonly name: string;
|
|
33
|
+
/**
|
|
34
|
+
* Listen for a specific event type.
|
|
35
|
+
* @param type
|
|
36
|
+
* @param handle
|
|
37
|
+
* @returns IUnsubscribable to remove the listener
|
|
38
|
+
*/
|
|
39
|
+
on<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): IUnsubscribable;
|
|
40
|
+
/**
|
|
41
|
+
* Listen for a specific event type, auto-removed after first call.
|
|
42
|
+
* @param type
|
|
43
|
+
* @param handle
|
|
44
|
+
* @returns IUnsubscribable to remove the listener
|
|
45
|
+
*/
|
|
46
|
+
once<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): IUnsubscribable;
|
|
47
|
+
/**
|
|
48
|
+
* Remove a listener for a specific event type.
|
|
49
|
+
* Alias: removeListener
|
|
50
|
+
* @param type
|
|
51
|
+
* @param handle
|
|
52
|
+
*/
|
|
53
|
+
off<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): void;
|
|
54
|
+
/**
|
|
55
|
+
* Remove a listener for a specific event type.
|
|
56
|
+
* @param type
|
|
57
|
+
* @param handle
|
|
58
|
+
*/
|
|
59
|
+
removeListener<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): void;
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to all event types.
|
|
62
|
+
* @param handle
|
|
63
|
+
* @param once If true, auto-removed after first call. Defaults to false.
|
|
64
|
+
* @returns IUnsubscribable to cancel the subscription
|
|
65
|
+
*/
|
|
66
|
+
subscribe<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(handle: IEventHandler<T, P, E>, once?: boolean): IUnsubscribable;
|
|
67
|
+
/**
|
|
68
|
+
* Cancel a subscription.
|
|
69
|
+
* @param handle
|
|
70
|
+
*/
|
|
71
|
+
unsubscribe<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(handle: IEventHandler<T, P, E>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Dispatch an event to all listeners and subscribers.
|
|
74
|
+
* Alias: emit
|
|
75
|
+
* @param evt
|
|
76
|
+
*/
|
|
77
|
+
dispatch<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(evt: Readonly<E>): void;
|
|
78
|
+
/**
|
|
79
|
+
* Dispatch an event to all listeners and subscribers.
|
|
80
|
+
* @param evt
|
|
81
|
+
*/
|
|
82
|
+
emit<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(evt: Readonly<E>): void;
|
|
83
|
+
/**
|
|
84
|
+
* Get the number of listeners.
|
|
85
|
+
* @param type If provided, count listeners for that type only. Otherwise, count all.
|
|
86
|
+
*/
|
|
87
|
+
listenerCount(type?: T): number;
|
|
88
|
+
/**
|
|
89
|
+
* Remove all listeners and subscribers.
|
|
90
|
+
*/
|
|
91
|
+
cleanup(): void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A simple event bus implementation with disposable support.
|
|
96
|
+
*
|
|
97
|
+
* Features:
|
|
98
|
+
* - Supports both type-specific listeners (on/once) and global subscribers (subscribe)
|
|
99
|
+
* - Returns IUnsubscribable for easy cleanup
|
|
100
|
+
* - Implements IBatchDisposable for resource management
|
|
101
|
+
* - Exception isolation: one handler's error won't stop other handlers
|
|
102
|
+
* - Listeners/subscribers are notified in registration order
|
|
103
|
+
*
|
|
104
|
+
* Note on `once` semantics:
|
|
105
|
+
* All `once` handlers are removed after each dispatch cycle completes.
|
|
106
|
+
* If a new `once` handler is registered during dispatch (e.g., inside another handler),
|
|
107
|
+
* it will also be removed at the end of that dispatch without being called.
|
|
108
|
+
* Use `on()` + manual `unsubscribe()` if you need more control over handler lifecycle.
|
|
109
|
+
*/
|
|
110
|
+
declare class EventBus<T extends IEventType> implements IEventBus<T> {
|
|
111
|
+
readonly name: string;
|
|
112
|
+
protected _disposed: boolean;
|
|
113
|
+
protected _listeners: Map<T, Array<IEventSubscription<T>>>;
|
|
114
|
+
protected _subscribers: Array<IEventSubscription<T>>;
|
|
115
|
+
protected _disposables: IDisposable[];
|
|
116
|
+
constructor(name: string);
|
|
117
|
+
get disposed(): boolean;
|
|
118
|
+
dispose(): void;
|
|
119
|
+
registerDisposable<D extends IDisposable>(disposable: D): void;
|
|
120
|
+
on<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): IUnsubscribable;
|
|
121
|
+
once<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): IUnsubscribable;
|
|
122
|
+
off<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): void;
|
|
123
|
+
removeListener<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>): void;
|
|
124
|
+
subscribe<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(handle: IEventHandler<T, P, E>, once?: boolean): IUnsubscribable;
|
|
125
|
+
unsubscribe<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(handle: IEventHandler<T, P, E>): void;
|
|
126
|
+
dispatch<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(evt: Readonly<E>): void;
|
|
127
|
+
emit<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(evt: Readonly<E>): void;
|
|
128
|
+
listenerCount(type?: T): number;
|
|
129
|
+
cleanup(): void;
|
|
130
|
+
protected _addListener<P extends IEventPayload = IEventPayload, E extends IEvent<T, P> = IEvent<T, P>>(type: T, handle: IEventHandler<T, P, E>, once: boolean): IUnsubscribable;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { EventBus };
|
|
134
|
+
export type { IEvent, IEventBus, IEventHandler, IEventPayload, IEventSubscription, IEventType };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guanghechen/eventbus",
|
|
3
|
+
"version": "7.0.1",
|
|
4
|
+
"description": "A simple event bus with disposable support.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "guanghechen",
|
|
7
|
+
"url": "https://github.com/guanghechen/"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/guanghechen/sora/tree/@guanghechen/eventbus@7.0.0",
|
|
12
|
+
"directory": "packages/eventbus"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/guanghechen/sora/tree/@guanghechen/eventbus@7.0.0/packages/eventbus#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"event bus",
|
|
17
|
+
"eventbus",
|
|
18
|
+
"pub-sub"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./lib/types/index.d.ts",
|
|
24
|
+
"source": "./src/index.ts",
|
|
25
|
+
"import": "./lib/esm/index.mjs",
|
|
26
|
+
"require": "./lib/cjs/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"source": "./src/index.ts",
|
|
30
|
+
"main": "./lib/cjs/index.cjs",
|
|
31
|
+
"module": "./lib/esm/index.mjs",
|
|
32
|
+
"types": "./lib/types/index.d.ts",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@guanghechen/types": "^2.0.1"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"lib/",
|
|
39
|
+
"!lib/**/*.map",
|
|
40
|
+
"package.json",
|
|
41
|
+
"CHANGELOG.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rollup -c ../../rollup.config.mjs",
|
|
47
|
+
"clean": "rimraf lib",
|
|
48
|
+
"test": "vitest run --config ../../vitest.config.ts",
|
|
49
|
+
"test:coverage": "vitest run --config ../../vitest.config.ts --coverage",
|
|
50
|
+
"test:update": "vitest run --config ../../vitest.config.ts -u"
|
|
51
|
+
}
|
|
52
|
+
}
|