@ublitzjs/channel 1.0.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/README.md +133 -0
- package/dist/cjs/index.js +159 -0
- package/dist/esm/index.js +151 -0
- package/dist/esm/package.json +1 -0
- package/dist/types/index.d.ts +110 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @ublitzjs/channel - REALLY fastest niche event emitter alternative
|
|
2
|
+
|
|
3
|
+
A blazing-fast, lightweight Pub/Sub event channel designed as a high-performance, type-safe alternative to Node's standard `EventEmitter`.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
<br/>
|
|
7
|
+
|
|
8
|
+
Built for performance-critical applications, it minimizes creation overhead and leverages an optimized swap-and-pop strategy to achieve **$O(1)$ unsubscription times** for unique listeners.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Ultra Low Overhead:** Faster instantiation and publication execution than standard event emitters.
|
|
13
|
+
- **$O(1)$ Removals:** True constant-time unsubscriptions via `sub_unique` and `unsub_unique`.
|
|
14
|
+
- **Self-Cleaning Listeners:** Easily create single-use (`once`) events mid-execution without breaking the dispatch loop.
|
|
15
|
+
- **No Dependencies:** Works in NodeJS, Bun and a Web browser
|
|
16
|
+
- **TypeScript-first**
|
|
17
|
+
- **Prebuilt for ESM and CJS**
|
|
18
|
+
- **Thoroughly tested**
|
|
19
|
+
|
|
20
|
+
> ⚠️ **Important Note on Ordering:** To maintain extreme speed during deletions, the execution order of the remaining listeners is **not preserved** when a subscriber is removed.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Benchmarks
|
|
25
|
+
- [NodeJS](./nodejs_benchmark.md)
|
|
26
|
+
- [Bun](./bunjs_benchmark.md)
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun add @ublitzjs/channel
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
### Using a single Channel
|
|
36
|
+
|
|
37
|
+
If you use EventEmitter for only one event, use **Channel** directly
|
|
38
|
+
```typescript
|
|
39
|
+
|
|
40
|
+
import { Channel } from '@ublitzjs/channel';
|
|
41
|
+
|
|
42
|
+
// Define your payload type
|
|
43
|
+
type LogMessage = `[Log] ${string}`;
|
|
44
|
+
|
|
45
|
+
// Create a channel with a payload type
|
|
46
|
+
const logChannel = new Channel<LogMessage>();
|
|
47
|
+
|
|
48
|
+
// Subscribe to updates (like emitter.on)
|
|
49
|
+
const onLog = (msg: LogMessage) => console.log(`Received: ${msg}`);
|
|
50
|
+
logChannel.sub(onLog);
|
|
51
|
+
|
|
52
|
+
// Publish data (like emitter.emit)
|
|
53
|
+
logChannel.pub("[Log] User logged in successfully");
|
|
54
|
+
|
|
55
|
+
// Unsubscribe (like emitter.off)
|
|
56
|
+
logChannel.unsub(onLog);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Using a ChannelMap (EventEmitter Style)
|
|
60
|
+
|
|
61
|
+
For multiple lazily created centralised events use **ChannelMap**
|
|
62
|
+
```typescript
|
|
63
|
+
|
|
64
|
+
import { ChannelMap } from '@ublitzjs/channel';
|
|
65
|
+
|
|
66
|
+
// 1. Define your Event Registry Map
|
|
67
|
+
interface AppEvents {
|
|
68
|
+
'user:login': { id: number; username: string };
|
|
69
|
+
'user:logout': { id: number };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Instantiate the Emitter Map
|
|
73
|
+
const emitter = new ChannelMap<AppEvents>();
|
|
74
|
+
|
|
75
|
+
// 3. Get the specific channel and subscribe
|
|
76
|
+
const loginChannel = emitter.on('user:login');
|
|
77
|
+
|
|
78
|
+
loginChannel.sub(({ username }) => {
|
|
79
|
+
console.log(`Welcome back, ${username}!`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 4. Emit/Publish an event
|
|
83
|
+
loginChannel.pub({ id: 42, username: 'Neo' });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Core Guide & Performance Optimization
|
|
87
|
+
1. Standard Subscription (sub / unsub)
|
|
88
|
+
|
|
89
|
+
Best used for listeners shared across multiple distinct channels or when you intend to subscribe the exact same function reference multiple times to a single channel.
|
|
90
|
+
|
|
91
|
+
Unsubscription Cost: O(N) linear scan from the end of the array.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
channel.sub(myListener);
|
|
95
|
+
channel.unsub(myListener);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
2. High-Performance Unique Subscription (sub_unique / unsub_unique)
|
|
99
|
+
|
|
100
|
+
Highly Recommended for hot code paths. By ensuring a listener is unique to this channel, the channel injects an internal tracking ID to achieve instant unsubscription.
|
|
101
|
+
|
|
102
|
+
Unsubscription Cost: O(1) constant time.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// The listener reference must be unique to this channel instance
|
|
106
|
+
const onDataReceived = (data) => processData(data);
|
|
107
|
+
|
|
108
|
+
channel.sub_unique(onDataReceived);
|
|
109
|
+
|
|
110
|
+
// Instantly removed in O(1) time complexity
|
|
111
|
+
channel.unsub_unique(onDataReceived);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
🔴 Warning: Do not manually modify or rely on the id property appended to your callback function in your external application logic.
|
|
115
|
+
|
|
116
|
+
3. Single-Use / Self-Cleaning Listeners (unsubCurrent)
|
|
117
|
+
|
|
118
|
+
Perfect for implementing once() behavior. If listener is It is advised to call it at the top of a listener
|
|
119
|
+
```TypeScript
|
|
120
|
+
|
|
121
|
+
channel.sub((data) => {
|
|
122
|
+
// Safely unsubscribes the callback while the channel is mid-publication
|
|
123
|
+
channel.unsubCurrent();
|
|
124
|
+
console.log("This will only run for the very first event dispatch:", data);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
channel.sub(async (data)=>{
|
|
128
|
+
channel.unsubCurrent();
|
|
129
|
+
await doSomething(data);
|
|
130
|
+
|
|
131
|
+
/* Do not call unsubCurrent() here */
|
|
132
|
+
})
|
|
133
|
+
```
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An optimized Pub/Sub event channel designed as a high-performance, scalable alternative to the standard `EventEmitter`.
|
|
5
|
+
* Features $O(1)$ removals for unique listeners and faster creation overhead.
|
|
6
|
+
* @template MessageType The type of data payload published by this channel.
|
|
7
|
+
* @note The order of listeners is **not preserved** when removing subscribers.
|
|
8
|
+
* @see Inspired by tseep.
|
|
9
|
+
* @example
|
|
10
|
+
* type LogMessage = `[Log] ${string}`;
|
|
11
|
+
* const logChannel = new Channel<LogMessage>();
|
|
12
|
+
* const onLog = (msg: LogMessage) => console.log(msg);
|
|
13
|
+
* logChannel.sub_unique(onLog);
|
|
14
|
+
* logChannel.pub("[Log] User logged in");
|
|
15
|
+
* logChannel.unsub_unique(onLog);
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", {
|
|
18
|
+
value: true
|
|
19
|
+
});
|
|
20
|
+
exports.ChannelMap = exports.Channel = void 0;
|
|
21
|
+
class Channel {
|
|
22
|
+
cbs = [];
|
|
23
|
+
i = 0;
|
|
24
|
+
/**
|
|
25
|
+
* Indicates whether the channel currently has no active listeners.
|
|
26
|
+
*/
|
|
27
|
+
get isEmpty() {
|
|
28
|
+
return !this.cbs.length;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Subscribes a listener to the channel.
|
|
32
|
+
* Use this for general listeners, that you reuse in many Channel instances OR when you need to register a listener several times OR or removal order doesn't matter.
|
|
33
|
+
* @example
|
|
34
|
+
* channel.sub((data) => console.log(data));
|
|
35
|
+
*/
|
|
36
|
+
sub(fn) {
|
|
37
|
+
this.cbs.push(fn);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Subscribes a unique listener to the channel and attaches an internal ID metadata property.
|
|
41
|
+
* Use this alongside `unsub_unique` to achieve constant time $O(1)$ unsubscription performance.
|
|
42
|
+
* @example
|
|
43
|
+
* const onEvent = (data) => console.log(data);
|
|
44
|
+
* channel.sub_unique(onEvent);
|
|
45
|
+
*/
|
|
46
|
+
sub_unique(fn) {
|
|
47
|
+
var cbs = this.cbs;
|
|
48
|
+
fn.id = cbs.length;
|
|
49
|
+
cbs.push(fn);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Unsubscribes a listener by performing a linear scan from the end of the array.
|
|
53
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
54
|
+
* @example
|
|
55
|
+
* channel.unsub(onEvent);
|
|
56
|
+
*/
|
|
57
|
+
unsub(fn) {
|
|
58
|
+
for (var i = this.cbs.length - 1; i >= 0; i--) {
|
|
59
|
+
if (this.cbs[i] != fn) continue;
|
|
60
|
+
if (i == this.cbs.length - 1) {
|
|
61
|
+
this.cbs.pop();
|
|
62
|
+
} else {
|
|
63
|
+
let newCb = this.cbs[i] = this.cbs.pop();
|
|
64
|
+
newCb.id = i;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Unsubscribes a listener previously registered via `sub_unique`.
|
|
71
|
+
* Leverages the internal `id` property to swap and pop elements in $O(1)$ time.
|
|
72
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
73
|
+
* @example
|
|
74
|
+
* channel.unsub_unique(onEvent);
|
|
75
|
+
*/
|
|
76
|
+
unsub_unique(fn) {
|
|
77
|
+
var id = fn.id;
|
|
78
|
+
if (id == this.cbs.length - 1) {
|
|
79
|
+
this.cbs.pop();
|
|
80
|
+
} else {
|
|
81
|
+
let newCb = this.cbs[id] = this.cbs.pop();
|
|
82
|
+
newCb.id = id;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Unsubscribes the listener that is **currently executing** during a publication cycle.
|
|
87
|
+
* Useful for writing single-use ("once") listeners or self-cleaning hooks.
|
|
88
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
89
|
+
* @example
|
|
90
|
+
* channel.sub((msg) => {
|
|
91
|
+
* console.log("Runs only once:", msg);
|
|
92
|
+
* channel.unsubCurrent();
|
|
93
|
+
* });
|
|
94
|
+
*/
|
|
95
|
+
unsubCurrent() {
|
|
96
|
+
var cbs = this.cbs;
|
|
97
|
+
if (this.i == cbs.length - 1) {
|
|
98
|
+
cbs.pop();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
;
|
|
102
|
+
var newCb = cbs[this.i] = cbs.pop();
|
|
103
|
+
newCb.id = this.i--;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Publishes data synchronously to all registered listeners.
|
|
107
|
+
* Listeners can be safely removed mid-execution via `unsubCurrent()`.
|
|
108
|
+
* @example
|
|
109
|
+
* channel.pub({ eventType: "click", x: 10, y: 20 });
|
|
110
|
+
*/
|
|
111
|
+
pub(data) {
|
|
112
|
+
var cbs = this.cbs;
|
|
113
|
+
while (this.i < cbs.length) {
|
|
114
|
+
cbs[this.i](data);
|
|
115
|
+
this.i++;
|
|
116
|
+
}
|
|
117
|
+
this.i = 0;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Drops all registered listeners from the channel instantly.
|
|
121
|
+
*/
|
|
122
|
+
clear() {
|
|
123
|
+
this.cbs = [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A strongly-typed map wrapper that dynamically manages distinct named `Channel` instances.
|
|
128
|
+
* Acts as a minimal simulation of EventEmitter
|
|
129
|
+
* For event names use should use strings performance-wise
|
|
130
|
+
* @template EventMap Type definition containing event names mapped to their respective payloads.
|
|
131
|
+
* @example
|
|
132
|
+
* interface Events {
|
|
133
|
+
* login: { username: string };
|
|
134
|
+
* }
|
|
135
|
+
* const emitter = new ChannelMap<Events>();
|
|
136
|
+
* var channel = emitter.on("login");
|
|
137
|
+
* channel.sub(({ username }) => console.log(username));
|
|
138
|
+
* channel.pub({ username: "Neo" });
|
|
139
|
+
*/
|
|
140
|
+
exports.Channel = Channel;
|
|
141
|
+
class ChannelMap {
|
|
142
|
+
/* Map get initialised lazily*/
|
|
143
|
+
events = {};
|
|
144
|
+
/**
|
|
145
|
+
* Retrieves or instantiates the specific `Channel` instance tied to an event name.
|
|
146
|
+
* @param ev The event name/key.
|
|
147
|
+
* @returns The `Channel` handling the event payload type.
|
|
148
|
+
*/
|
|
149
|
+
on(ev) {
|
|
150
|
+
return this.events[ev] ??= new Channel();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* @param ev The optional event name/key. If present - clears the channel for event. Otherwise - remove all channels
|
|
154
|
+
*/
|
|
155
|
+
removeAllListeners(ev) {
|
|
156
|
+
ev ? this.events[ev]?.clear() : this.events = {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.ChannelMap = ChannelMap;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* An optimized Pub/Sub event channel designed as a high-performance, scalable alternative to the standard `EventEmitter`.
|
|
4
|
+
* Features $O(1)$ removals for unique listeners and faster creation overhead.
|
|
5
|
+
* @template MessageType The type of data payload published by this channel.
|
|
6
|
+
* @note The order of listeners is **not preserved** when removing subscribers.
|
|
7
|
+
* @see Inspired by tseep.
|
|
8
|
+
* @example
|
|
9
|
+
* type LogMessage = `[Log] ${string}`;
|
|
10
|
+
* const logChannel = new Channel<LogMessage>();
|
|
11
|
+
* const onLog = (msg: LogMessage) => console.log(msg);
|
|
12
|
+
* logChannel.sub_unique(onLog);
|
|
13
|
+
* logChannel.pub("[Log] User logged in");
|
|
14
|
+
* logChannel.unsub_unique(onLog);
|
|
15
|
+
*/
|
|
16
|
+
export class Channel {
|
|
17
|
+
cbs = [];
|
|
18
|
+
i = 0;
|
|
19
|
+
/**
|
|
20
|
+
* Indicates whether the channel currently has no active listeners.
|
|
21
|
+
*/
|
|
22
|
+
get isEmpty() { return !this.cbs.length; }
|
|
23
|
+
/**
|
|
24
|
+
* Subscribes a listener to the channel.
|
|
25
|
+
* Use this for general listeners, that you reuse in many Channel instances OR when you need to register a listener several times OR or removal order doesn't matter.
|
|
26
|
+
* @example
|
|
27
|
+
* channel.sub((data) => console.log(data));
|
|
28
|
+
*/
|
|
29
|
+
sub(fn) {
|
|
30
|
+
this.cbs.push(fn);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Subscribes a unique listener to the channel and attaches an internal ID metadata property.
|
|
34
|
+
* Use this alongside `unsub_unique` to achieve constant time $O(1)$ unsubscription performance.
|
|
35
|
+
* @example
|
|
36
|
+
* const onEvent = (data) => console.log(data);
|
|
37
|
+
* channel.sub_unique(onEvent);
|
|
38
|
+
*/
|
|
39
|
+
sub_unique(fn) {
|
|
40
|
+
var cbs = this.cbs;
|
|
41
|
+
fn.id = cbs.length;
|
|
42
|
+
cbs.push(fn);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Unsubscribes a listener by performing a linear scan from the end of the array.
|
|
46
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
47
|
+
* @example
|
|
48
|
+
* channel.unsub(onEvent);
|
|
49
|
+
*/
|
|
50
|
+
unsub(fn) {
|
|
51
|
+
for (var i = this.cbs.length - 1; i >= 0; i--) {
|
|
52
|
+
if (this.cbs[i] != fn)
|
|
53
|
+
continue;
|
|
54
|
+
if (i == this.cbs.length - 1) {
|
|
55
|
+
this.cbs.pop();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
let newCb = (this.cbs[i] = this.cbs.pop());
|
|
59
|
+
newCb.id = i;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Unsubscribes a listener previously registered via `sub_unique`.
|
|
66
|
+
* Leverages the internal `id` property to swap and pop elements in $O(1)$ time.
|
|
67
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
68
|
+
* @example
|
|
69
|
+
* channel.unsub_unique(onEvent);
|
|
70
|
+
*/
|
|
71
|
+
unsub_unique(fn) {
|
|
72
|
+
var id = fn.id;
|
|
73
|
+
if (id == this.cbs.length - 1) {
|
|
74
|
+
this.cbs.pop();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
let newCb = (this.cbs[id] = this.cbs.pop());
|
|
78
|
+
newCb.id = id;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Unsubscribes the listener that is **currently executing** during a publication cycle.
|
|
83
|
+
* Useful for writing single-use ("once") listeners or self-cleaning hooks.
|
|
84
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
85
|
+
* @example
|
|
86
|
+
* channel.sub((msg) => {
|
|
87
|
+
* console.log("Runs only once:", msg);
|
|
88
|
+
* channel.unsubCurrent();
|
|
89
|
+
* });
|
|
90
|
+
*/
|
|
91
|
+
unsubCurrent() {
|
|
92
|
+
var cbs = this.cbs;
|
|
93
|
+
if (this.i == cbs.length - 1) {
|
|
94
|
+
cbs.pop();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
;
|
|
98
|
+
var newCb = (cbs[this.i] = cbs.pop());
|
|
99
|
+
newCb.id = this.i--;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Publishes data synchronously to all registered listeners.
|
|
103
|
+
* Listeners can be safely removed mid-execution via `unsubCurrent()`.
|
|
104
|
+
* @example
|
|
105
|
+
* channel.pub({ eventType: "click", x: 10, y: 20 });
|
|
106
|
+
*/
|
|
107
|
+
pub(data) {
|
|
108
|
+
var cbs = this.cbs;
|
|
109
|
+
while (this.i < cbs.length) {
|
|
110
|
+
cbs[this.i](data);
|
|
111
|
+
this.i++;
|
|
112
|
+
}
|
|
113
|
+
this.i = 0;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Drops all registered listeners from the channel instantly.
|
|
117
|
+
*/
|
|
118
|
+
clear() { this.cbs = []; }
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A strongly-typed map wrapper that dynamically manages distinct named `Channel` instances.
|
|
122
|
+
* Acts as a minimal simulation of EventEmitter
|
|
123
|
+
* For event names use should use strings performance-wise
|
|
124
|
+
* @template EventMap Type definition containing event names mapped to their respective payloads.
|
|
125
|
+
* @example
|
|
126
|
+
* interface Events {
|
|
127
|
+
* login: { username: string };
|
|
128
|
+
* }
|
|
129
|
+
* const emitter = new ChannelMap<Events>();
|
|
130
|
+
* var channel = emitter.on("login");
|
|
131
|
+
* channel.sub(({ username }) => console.log(username));
|
|
132
|
+
* channel.pub({ username: "Neo" });
|
|
133
|
+
*/
|
|
134
|
+
export class ChannelMap {
|
|
135
|
+
/* Map get initialised lazily*/
|
|
136
|
+
events = {};
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves or instantiates the specific `Channel` instance tied to an event name.
|
|
139
|
+
* @param ev The event name/key.
|
|
140
|
+
* @returns The `Channel` handling the event payload type.
|
|
141
|
+
*/
|
|
142
|
+
on(ev) {
|
|
143
|
+
return this.events[ev] ??= new Channel();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @param ev The optional event name/key. If present - clears the channel for event. Otherwise - remove all channels
|
|
147
|
+
*/
|
|
148
|
+
removeAllListeners(ev) {
|
|
149
|
+
ev ? this.events[ev]?.clear() : this.events = {};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "module"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A callback function used as a listener in a `Channel`.
|
|
3
|
+
* * @template T The type of data payload this callback receives.
|
|
4
|
+
* * @note This type is mutated internally with an `id` property to achieve $O(1)$ lookup times during unsubscription.
|
|
5
|
+
* **Do not manually modify or rely on the `id` property in external code.**
|
|
6
|
+
*/
|
|
7
|
+
export type ChannelCB<T> = ((data: T) => void) & {
|
|
8
|
+
id?: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* An optimized Pub/Sub event channel designed as a high-performance, scalable alternative to the standard `EventEmitter`.
|
|
12
|
+
* Features $O(1)$ removals for unique listeners and faster creation overhead.
|
|
13
|
+
* @template MessageType The type of data payload published by this channel.
|
|
14
|
+
* @note The order of listeners is **not preserved** when removing subscribers.
|
|
15
|
+
* @see Inspired by tseep.
|
|
16
|
+
* @example
|
|
17
|
+
* type LogMessage = `[Log] ${string}`;
|
|
18
|
+
* const logChannel = new Channel<LogMessage>();
|
|
19
|
+
* const onLog = (msg: LogMessage) => console.log(msg);
|
|
20
|
+
* logChannel.sub_unique(onLog);
|
|
21
|
+
* logChannel.pub("[Log] User logged in");
|
|
22
|
+
* logChannel.unsub_unique(onLog);
|
|
23
|
+
*/
|
|
24
|
+
export declare class Channel<MessageType> {
|
|
25
|
+
protected cbs: ChannelCB<MessageType>[];
|
|
26
|
+
protected i: number;
|
|
27
|
+
/**
|
|
28
|
+
* Indicates whether the channel currently has no active listeners.
|
|
29
|
+
*/
|
|
30
|
+
get isEmpty(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Subscribes a listener to the channel.
|
|
33
|
+
* Use this for general listeners, that you reuse in many Channel instances OR when you need to register a listener several times OR or removal order doesn't matter.
|
|
34
|
+
* @example
|
|
35
|
+
* channel.sub((data) => console.log(data));
|
|
36
|
+
*/
|
|
37
|
+
sub(fn: ChannelCB<MessageType>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribes a unique listener to the channel and attaches an internal ID metadata property.
|
|
40
|
+
* Use this alongside `unsub_unique` to achieve constant time $O(1)$ unsubscription performance.
|
|
41
|
+
* @example
|
|
42
|
+
* const onEvent = (data) => console.log(data);
|
|
43
|
+
* channel.sub_unique(onEvent);
|
|
44
|
+
*/
|
|
45
|
+
sub_unique(fn: ChannelCB<MessageType>): void;
|
|
46
|
+
/**
|
|
47
|
+
* Unsubscribes a listener by performing a linear scan from the end of the array.
|
|
48
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
49
|
+
* @example
|
|
50
|
+
* channel.unsub(onEvent);
|
|
51
|
+
*/
|
|
52
|
+
unsub(fn: ChannelCB<MessageType>): void;
|
|
53
|
+
/**
|
|
54
|
+
* Unsubscribes a listener previously registered via `sub_unique`.
|
|
55
|
+
* Leverages the internal `id` property to swap and pop elements in $O(1)$ time.
|
|
56
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
57
|
+
* @example
|
|
58
|
+
* channel.unsub_unique(onEvent);
|
|
59
|
+
*/
|
|
60
|
+
unsub_unique(fn: ChannelCB<MessageType>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Unsubscribes the listener that is **currently executing** during a publication cycle.
|
|
63
|
+
* Useful for writing single-use ("once") listeners or self-cleaning hooks.
|
|
64
|
+
* @warning This method alters the execution order of the remaining listeners.
|
|
65
|
+
* @example
|
|
66
|
+
* channel.sub((msg) => {
|
|
67
|
+
* console.log("Runs only once:", msg);
|
|
68
|
+
* channel.unsubCurrent();
|
|
69
|
+
* });
|
|
70
|
+
*/
|
|
71
|
+
unsubCurrent(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Publishes data synchronously to all registered listeners.
|
|
74
|
+
* Listeners can be safely removed mid-execution via `unsubCurrent()`.
|
|
75
|
+
* @example
|
|
76
|
+
* channel.pub({ eventType: "click", x: 10, y: 20 });
|
|
77
|
+
*/
|
|
78
|
+
pub(data: MessageType): void;
|
|
79
|
+
/**
|
|
80
|
+
* Drops all registered listeners from the channel instantly.
|
|
81
|
+
*/
|
|
82
|
+
clear(): void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A strongly-typed map wrapper that dynamically manages distinct named `Channel` instances.
|
|
86
|
+
* Acts as a minimal simulation of EventEmitter
|
|
87
|
+
* For event names use should use strings performance-wise
|
|
88
|
+
* @template EventMap Type definition containing event names mapped to their respective payloads.
|
|
89
|
+
* @example
|
|
90
|
+
* interface Events {
|
|
91
|
+
* login: { username: string };
|
|
92
|
+
* }
|
|
93
|
+
* const emitter = new ChannelMap<Events>();
|
|
94
|
+
* var channel = emitter.on("login");
|
|
95
|
+
* channel.sub(({ username }) => console.log(username));
|
|
96
|
+
* channel.pub({ username: "Neo" });
|
|
97
|
+
*/
|
|
98
|
+
export declare class ChannelMap<EventMap extends Record<string | number | symbol, any>> {
|
|
99
|
+
protected events: EventMap;
|
|
100
|
+
/**
|
|
101
|
+
* Retrieves or instantiates the specific `Channel` instance tied to an event name.
|
|
102
|
+
* @param ev The event name/key.
|
|
103
|
+
* @returns The `Channel` handling the event payload type.
|
|
104
|
+
*/
|
|
105
|
+
on<Event extends keyof EventMap>(ev: Event): Channel<EventMap[Event]>;
|
|
106
|
+
/**
|
|
107
|
+
* @param ev The optional event name/key. If present - clears the channel for event. Otherwise - remove all channels
|
|
108
|
+
*/
|
|
109
|
+
removeAllListeners(ev?: keyof EventMap): void;
|
|
110
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ublitzjs/channel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"types": "./dist/types/index.d.ts",
|
|
5
|
+
"files": ["dist", "LICENSE"],
|
|
6
|
+
"typesVersions": {
|
|
7
|
+
"*": {
|
|
8
|
+
".": ["./dist/types/index.d.ts"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/types/index.d.ts",
|
|
15
|
+
"require": "./dist/cjs/index.js",
|
|
16
|
+
"import": "./dist/esm/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/cli": "^7.28.6",
|
|
24
|
+
"@babel/core": "^7.29.0",
|
|
25
|
+
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
|
26
|
+
"@types/node": "^25.9.1",
|
|
27
|
+
"cozyevent": "^1.3.3",
|
|
28
|
+
"esbuild": "^0.25.12",
|
|
29
|
+
"tinybench": "^6.0.0",
|
|
30
|
+
"tsd": "^0.33.0",
|
|
31
|
+
"tseep": "^1.3.1"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"trick-tsd": "bun link && bun link @ublitzjs/channel",
|
|
35
|
+
"build:esm": "tsc -p tsconfig.esm.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
|
|
36
|
+
"build:cjs": "babel dist/esm --out-dir dist/cjs",
|
|
37
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
38
|
+
"build": "npm run build:types && npm run build:esm && npm run build:cjs",
|
|
39
|
+
"test": "tsd && vitest run tests/index.test.ts --coverage",
|
|
40
|
+
"bench:node": "node tests/bench.mjs",
|
|
41
|
+
"bench:bun": "bun tests/bench.mjs"
|
|
42
|
+
},
|
|
43
|
+
"tsd": {
|
|
44
|
+
"directory": "tests"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/ublitzjs/channel.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/ublitzjs/channel/issues",
|
|
52
|
+
"email": "diril656@gmail.com"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/ublitzjs/channel#readme",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"ublitzjs",
|
|
57
|
+
"typescript",
|
|
58
|
+
"development",
|
|
59
|
+
"pub/sub",
|
|
60
|
+
"fastest event emitter",
|
|
61
|
+
"O(1)",
|
|
62
|
+
"asynchronous code",
|
|
63
|
+
"µBlitz.js"
|
|
64
|
+
],
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
67
|
+
"vitest": "^4.1.7"
|
|
68
|
+
}
|
|
69
|
+
}
|