zephyr-events 1.1.2 → 1.3.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 CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2021 Jason Miller
4
+ Copyright (c) 2025 ebogdum
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,211 +1,318 @@
1
- # 🌊ïļ Zephyr Events
1
+ # Zephyr Events — Tiny TypeScript Event Emitter
2
2
 
3
- Ultra-fast ES2023 event emitter with **905B** bundle size and race-condition safety.
3
+ A lightweight, type-safe event emitter for TypeScript and JavaScript. Zero dependencies, under 2KB, with built-in race-condition safety that larger alternatives like EventEmitter3 and Node.js EventEmitter lack.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/zephyr-events.svg)](https://www.npmjs.com/package/zephyr-events)
6
- [![Bundle Size](https://img.shields.io/badge/size-905B-brightgreen.svg)](https://bundlephobia.com/package/zephyr-events)
6
+ [![npm downloads](https://img.shields.io/npm/dm/zephyr-events.svg)](https://www.npmjs.com/package/zephyr-events)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/zephyr-events)](https://bundlephobia.com/package/zephyr-events)
7
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
+ [![license](https://img.shields.io/npm/l/zephyr-events.svg)](https://github.com/ebogdum/zephyr-events/blob/main/LICENSE)
8
10
 
9
- ---
11
+ ## Why Zephyr Events?
10
12
 
11
- ## ⚡ Key Features
13
+ Most event emitter libraries break when handlers modify the listener list during emission — a handler that calls `off()` on itself can skip the next handler, or adding a new listener mid-emit can cause infinite loops. Zephyr Events solves this with snapshot-based iteration, making it safe for real-world use in UI frameworks, state machines, and plugin systems.
12
14
 
13
- - **ðŸ”Ĩ Ultra Fast**: 33M+ operations/second with native Set/Map optimizations
14
- - **ðŸŠķ Tiny Bundle**: Only 905B minified, 0 dependencies
15
- - **ðŸ›Ąïļ Race-Condition Safe**: Immutable snapshots prevent handler modification issues
16
- - **ðŸŽŊ ES2023 Native**: Optional chaining, nullish coalescing, spread operators
17
- - **ðŸ“Ķ Tree Shakeable**: ES modules with proper exports
18
- - **🔧 TypeScript**: Full type safety with generics and strict types
15
+ - **Under 2KB** — 1.9KB ESM, zero dependencies, tree-shakeable
16
+ - **Race-condition safe** — handlers can subscribe, unsubscribe, or clear during emit without side effects
17
+ - **Fast mode** — `zephyrEventsFast` drops snapshot safety for up to 82% faster emit
18
+ - **Full TypeScript support** — generic event maps, strict handler signatures, IDE autocompletion
19
+ - **Universal builds** — ESM, CommonJS, and UMD for browsers, Node.js, and bundlers
20
+ - **Wildcard listeners** — subscribe to all events with `*`
21
+ - **Shared state** — pass a handler map between emitters for cross-module communication
19
22
 
20
- ---
21
-
22
- ## ðŸ“Ĩ Installation
23
+ ## Installation
23
24
 
24
25
  ```bash
25
26
  npm install zephyr-events
26
27
  ```
27
28
 
28
- ---
29
+ ```bash
30
+ yarn add zephyr-events
31
+ ```
32
+
33
+ ```bash
34
+ pnpm add zephyr-events
35
+ ```
36
+
37
+ ## Quick Start
29
38
 
30
- ## 🚀 Quick Start
39
+ ### TypeScript Event Emitter
31
40
 
32
41
  ```typescript
33
42
  import zephyrEvents from 'zephyr-events';
34
43
 
35
- // Create typed emitter
36
- type Events = {
37
- user: { id: number; name: string }
38
- error: Error
44
+ // Define your event types
45
+ type AppEvents = {
46
+ 'user:login': { id: number; name: string }
47
+ 'user:logout': { id: number }
48
+ 'error': Error
39
49
  }
40
50
 
41
- const emitter = zephyrEvents<Events>();
51
+ const emitter = zephyrEvents<AppEvents>();
42
52
 
43
- // Subscribe with auto-cleanup
44
- const unsubscribe = emitter.on('user', (user) => {
45
- console.log(`User: ${user.name}`);
53
+ // Subscribe — returns an unsubscribe function for easy cleanup
54
+ const unsubscribe = emitter.on('user:login', (user) => {
55
+ console.log(`Welcome, ${user.name}`);
46
56
  });
47
57
 
48
- // Emit events
49
- emitter.emit('user', { id: 1, name: 'Alice' });
58
+ // Emit with full type checking
59
+ emitter.emit('user:login', { id: 1, name: 'Alice' });
50
60
 
51
- // Cleanup
61
+ // Clean up when done
52
62
  unsubscribe();
53
63
  ```
54
64
 
55
- ---
65
+ ### Fast Mode
56
66
 
57
- ## ðŸŽĻ API Reference
67
+ When handlers never modify the listener list during emit, use `zephyrEventsFast` for maximum throughput:
58
68
 
59
- ### `zephyrEvents<Events>()`
69
+ ```typescript
70
+ import { zephyrEventsFast } from 'zephyr-events';
71
+
72
+ const emitter = zephyrEventsFast<AppEvents>();
73
+ // Same API — on(), off(), emit(), all
74
+ ```
60
75
 
61
- Create a new event emitter instance.
76
+ ### JavaScript Event Emitter
77
+
78
+ ```javascript
79
+ const zephyrEvents = require('zephyr-events');
80
+ const { zephyrEventsFast } = require('zephyr-events');
81
+
82
+ const emitter = zephyrEvents(); // safe (snapshot)
83
+ const fast = zephyrEventsFast(); // fast (no snapshot)
84
+
85
+ emitter.on('message', (data) => {
86
+ console.log('Received:', data);
87
+ });
88
+
89
+ emitter.emit('message', { text: 'Hello' });
90
+ ```
91
+
92
+ ## API Reference
93
+
94
+ ### `zephyrEvents<Events>(all?)`
95
+
96
+ Creates a new typed event emitter with snapshot-safe emission. Optionally accepts an existing handler map to share event state between emitters.
62
97
 
63
98
  ```typescript
64
99
  const emitter = zephyrEvents<{
65
100
  message: string
66
101
  data: { value: number }
67
102
  }>();
103
+
104
+ // Share handlers between emitters
105
+ const shared = new Map();
106
+ const emitterA = zephyrEvents(shared);
107
+ const emitterB = zephyrEvents(shared);
68
108
  ```
69
109
 
70
- ### `emitter.on(type, handler)`
110
+ ### `zephyrEventsFast<Events>(all?)`
71
111
 
72
- Register event handler. Returns unsubscribe function.
112
+ Creates a non-snapshot event emitter. Same API as `zephyrEvents`, but `emit()` iterates the live handler array instead of a copy. Up to 82% faster for single-handler emit. Use when handlers do not add/remove listeners during emission.
73
113
 
74
114
  ```typescript
115
+ import { zephyrEventsFast } from 'zephyr-events';
116
+
117
+ const emitter = zephyrEventsFast<{ tick: number }>();
118
+ ```
119
+
120
+ ### `emitter.on(type, handler): Unsubscribe`
121
+
122
+ Registers an event handler. Returns an unsubscribe function for automatic cleanup — no need to keep a reference to the handler.
123
+
124
+ ```typescript
125
+ // Type-safe subscription
75
126
  const unsub = emitter.on('message', (msg) => {
76
127
  console.log(msg);
77
128
  });
78
129
 
79
- // Wildcard listener
130
+ // Wildcard listener — receives every event
80
131
  emitter.on('*', (type, event) => {
81
- console.log(`Event ${type}:`, event);
132
+ console.log(`[${String(type)}]`, event);
82
133
  });
134
+
135
+ // Unsubscribe when no longer needed
136
+ unsub();
83
137
  ```
84
138
 
85
139
  ### `emitter.off(type, handler?)`
86
140
 
87
- Remove event handler(s).
141
+ Removes a specific handler, or all handlers for an event type.
88
142
 
89
143
  ```typescript
90
- // Remove specific handler
91
- emitter.off('message', handler);
144
+ // Remove a specific handler
145
+ emitter.off('message', myHandler);
92
146
 
93
- // Remove all handlers for type
147
+ // Remove all handlers for an event type
94
148
  emitter.off('message');
95
149
  ```
96
150
 
97
151
  ### `emitter.emit(type, event)`
98
152
 
99
- Emit event to all registered handlers.
153
+ Emits an event to all registered handlers. Handlers run synchronously from a snapshot, so the listener list can be safely modified during emission.
100
154
 
101
155
  ```typescript
102
156
  emitter.emit('message', 'Hello World!');
103
157
  emitter.emit('data', { value: 42 });
104
158
  ```
105
159
 
106
- ---
160
+ ### `emitter.all`
161
+
162
+ The underlying `Map<EventType, Handler[]>` that stores all registered handlers. Can be inspected, serialized, or shared between emitter instances.
107
163
 
108
- ## 🏗ïļ Technical Details
164
+ ## Use Cases
109
165
 
110
- ### Architecture
166
+ ### Event Bus for React Components
167
+
168
+ ```typescript
169
+ import zephyrEvents from 'zephyr-events';
111
170
 
112
- Zephyr Events uses a **dual-storage architecture** for maximum performance:
171
+ type UIEvents = {
172
+ 'modal:open': { id: string }
173
+ 'modal:close': { id: string }
174
+ 'toast': { message: string; severity: 'info' | 'error' }
175
+ }
113
176
 
114
- - **Set**: O(1) add/remove operations
115
- - **Array snapshots**: Fast iteration with race-condition safety
116
- - **ES2023 optimizations**: Native optional chaining and nullish coalescing
177
+ // Create a shared event bus
178
+ export const uiBus = zephyrEvents<UIEvents>();
117
179
 
118
- ### Race-Condition Safety
180
+ // In a React component — clean up on unmount
181
+ useEffect(() => {
182
+ const unsub = uiBus.on('toast', (toast) => {
183
+ showToast(toast.message, toast.severity);
184
+ });
185
+ return unsub;
186
+ }, []);
187
+ ```
119
188
 
120
- Handlers are executed from immutable snapshots:
189
+ ### Pub/Sub in Node.js Microservices
121
190
 
122
191
  ```typescript
123
- emitter.on('test', function selfRemover() {
124
- emitter.off('test', selfRemover); // Safe during emit
192
+ import zephyrEvents from 'zephyr-events';
193
+
194
+ type ServiceEvents = {
195
+ 'order:created': { orderId: string; total: number }
196
+ 'order:shipped': { orderId: string; trackingId: string }
197
+ 'inventory:low': { sku: string; remaining: number }
198
+ }
199
+
200
+ const events = zephyrEvents<ServiceEvents>();
201
+
202
+ // Multiple subscribers
203
+ events.on('order:created', sendConfirmationEmail);
204
+ events.on('order:created', updateAnalytics);
205
+ events.on('inventory:low', notifyWarehouse);
206
+
207
+ // Audit logging with wildcard
208
+ events.on('*', (type, data) => {
209
+ logger.info({ event: type, payload: data });
125
210
  });
126
211
  ```
127
212
 
128
- ### ES2023 Features
213
+ ### Plugin System
214
+
215
+ ```typescript
216
+ import zephyrEvents from 'zephyr-events';
129
217
 
130
- - **Nullish coalescing**: `all ??= new Map()`
131
- - **Optional chaining**: `handlers?.size`
132
- - **Spread operators**: `[...handlers]` for fast snapshots
218
+ type PluginEvents = {
219
+ 'init': { config: Record<string, unknown> }
220
+ 'transform': { input: string }
221
+ 'destroy': undefined
222
+ }
133
223
 
134
- ### Bundle Formats
224
+ function createPluginHost() {
225
+ const emitter = zephyrEvents<PluginEvents>();
135
226
 
136
- - **ESM**: `dist/zephyr-events.mjs` (905B)
137
- - **CommonJS**: `dist/zephyr-events.js` (977B)
138
- - **UMD**: `dist/zephyr-events.umd.js` (1.3KB)
227
+ return {
228
+ register(plugin: (on: typeof emitter.on) => void) {
229
+ plugin(emitter.on.bind(emitter));
230
+ },
231
+ emit: emitter.emit.bind(emitter),
232
+ };
233
+ }
234
+ ```
139
235
 
140
- ---
236
+ ## Race-Condition Safety
141
237
 
142
- ## 🆚 Comparison
238
+ Unlike most event emitters, Zephyr Events handles listener modification during emission safely. Each `emit()` call iterates over a snapshot of the handler list, so adding or removing handlers mid-emit never causes skipped handlers or infinite loops:
143
239
 
144
- | Feature | Zephyr Events | mitt* | eventemitter3 |
145
- |---------|---------------|------|---------------|
146
- | Bundle Size | 905B | 200B | 7KB |
147
- | TypeScript | ✅ Native | ✅ | ✅ |
148
- | Race-Safe | ✅ | ❌ | ❌ |
149
- | ES2023 | ✅ | ❌ | ❌ |
150
- | Performance | 33M ops/s | 15M ops/s | 10M ops/s |
240
+ ```typescript
241
+ const emitter = zephyrEvents();
151
242
 
152
- *\*Based on original mitt package by Jason Miller*
243
+ // Safe: handler removes itself during emit
244
+ emitter.on('data', function once(data) {
245
+ emitter.off('data', once);
246
+ process(data); // next handler still fires
247
+ });
153
248
 
154
- ---
249
+ // Safe: handler adds new listeners during emit
250
+ emitter.on('init', () => {
251
+ emitter.on('init', () => {
252
+ // this will NOT fire during the current emit cycle
253
+ });
254
+ });
255
+ ```
155
256
 
156
- ## 🚀 Performance Benchmarks
257
+ ## Comparison with Other Event Emitters
157
258
 
158
- Comprehensive performance benchmarks on **Apple Silicon M-series (ARM64)** with **Node.js v23.10.0**:
259
+ | Feature | Zephyr Events | mitt | EventEmitter3 | Node.js EventEmitter |
260
+ |---------|:---:|:---:|:---:|:---:|
261
+ | Bundle size | ~2KB | ~200B | ~7KB | Built-in |
262
+ | TypeScript types | Native | Native | Bundled | `@types/node` |
263
+ | Race-condition safe | Yes | No | No | No |
264
+ | Fast (no-snapshot) mode | Yes | No | No | No |
265
+ | Wildcard listeners | Yes | Yes | No | No |
266
+ | Unsubscribe function | Yes | No | No | No |
267
+ | Shared handler maps | Yes | Yes | No | No |
268
+ | Zero dependencies | Yes | Yes | Yes | N/A |
269
+ | Tree-shakeable ESM | Yes | Yes | Yes | No |
159
270
 
160
- ### Core Operations Performance
271
+ ## Performance Benchmarks
161
272
 
162
- | Operation | Ops/Second | Description |
163
- |-----------|------------|-------------|
164
- | **Emitter Creation** | **10.54M** | Creating new emitter instances |
165
- | **Single Handler Emit** | **33.69M** | Emitting to one event handler |
166
- | **Wildcard Emit** | **26.12M** | Emitting to wildcard listeners |
167
- | **10 Handlers Emit** | **9.32M** | Emitting to 10 concurrent handlers |
168
- | **100 Handlers Emit** | **1.57M** | Emitting to 100 concurrent handlers |
169
- | **Mixed Operations** | **7.17M** | Realistic usage: on/emit/off cycle |
273
+ Tested on Apple Silicon M-series (ARM64), Node.js v25.2.1. Median of 3 runs in isolated V8 processes. Run `node benchmark-compare.js` to reproduce.
170
274
 
171
- ### Management Operations Performance
275
+ ### Safe vs Fast
172
276
 
173
- | Operation | Ops/Second | Description |
174
- |-----------|------------|-------------|
175
- | **Off Method** | **194.17M** | Removing specific handler with `.off()` |
176
- | **Unsubscribe** | **143.54M** | Removing handler with returned function |
177
- | **Event Subscription** | **9.19K** | Adding new event handlers with `.on()` |
178
- | **Memory Stress** | **130** | Complex multi-event scenario |
277
+ | Operation | Safe (snapshot) | Fast (no snapshot) | Delta |
278
+ |-----------|----------------:|-------------------:|------:|
279
+ | **Emit (1 handler)** | 49.4M ops/s | 89.9M ops/s | **+82%** |
280
+ | **Emit (10 handlers)** | 14.0M ops/s | 16.9M ops/s | **+21%** |
281
+ | **Emit (100 handlers)** | 1.9M ops/s | 2.1M ops/s | **+12%** |
282
+ | **Wildcard emit** | 38.4M ops/s | 62.7M ops/s | **+63%** |
283
+ | **Emit (no wildcards)** | 36.3M ops/s | 57.5M ops/s | **+59%** |
284
+ | **On + unsub cycle** | 11.0M ops/s | 11.0M ops/s | 0% |
285
+ | **Off (specific handler)** | 11.8M ops/s | 11.7M ops/s | 0% |
286
+ | **Mixed (on/emit/unsub)** | 8.6M ops/s | 9.3M ops/s | **+8%** |
179
287
 
180
- ### Key Performance Insights
288
+ All benchmarks use proper setup/run separation with warmup passes. No-op handlers measure emitter overhead only — real-world throughput depends on handler complexity.
181
289
 
182
- - **ðŸ”Ĩ Ultra-fast emission**: Up to **33.69M operations/second** for single handlers
183
- - **⚡ Instant cleanup**: Handler removal at **194.17M operations/second**
184
- - **📈 Scales efficiently**: Maintains high performance with multiple handlers
185
- - **ðŸ›Ąïļ Race-condition safe**: Minimal overhead for safety guarantees
186
- - **ðŸŽŊ Real-world optimized**: **7.17M ops/sec** for typical usage patterns
290
+ ## Bundle Formats
187
291
 
188
- ### Architecture Benefits
292
+ Zephyr Events ships three bundle formats for maximum compatibility:
189
293
 
190
- - **Dual Storage**: Set for O(1) add/remove + Array snapshots for fast iteration
191
- - **ES2023 Native**: Optional chaining (`?.`) and nullish coalescing (`??`) optimizations
192
- - **Memory Efficient**: Stable performance under stress conditions
193
- - **Zero Dependencies**: Pure JavaScript with no external overhead
294
+ | Format | File | Use case |
295
+ |--------|------|----------|
296
+ | ESM | `dist/zephyr-events.mjs` | Modern bundlers (Vite, Rollup, webpack 5+) |
297
+ | CommonJS | `dist/zephyr-events.js` | Node.js `require()`, older bundlers |
298
+ | UMD | `dist/zephyr-events.umd.js` | Script tags, AMD loaders, legacy environments |
194
299
 
195
- ---
300
+ ## Requirements
196
301
 
197
- ## 🙏 Acknowledgments
302
+ - **Node.js** >= 18
303
+ - **TypeScript** >= 4.7 (for type features; runtime has no TS dependency)
304
+ - Works in all modern browsers (ES2020+ support)
198
305
 
199
- **Zephyr Events** is a heavy modernization and performance upgrade of the original [mitt](https://github.com/developit/mitt) package by [Jason Miller](https://github.com/developit). Thanks for the foundational work!
306
+ ## Contributing
200
307
 
201
- ---
308
+ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
202
309
 
203
- ## ðŸĪ Contributing
310
+ ## Acknowledgments
204
311
 
205
- Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md).
312
+ Zephyr Events is a modernization of [mitt](https://github.com/developit/mitt) by [Jason Miller](https://github.com/developit). Built on the same simple API with added type safety, race-condition handling, and universal module support.
206
313
 
207
- ## 📄 License
314
+ ## License
208
315
 
209
- MIT ÂĐ [ebogdum](https://github.com/ebogdum)
316
+ [MIT](LICENSE)
210
317
 
211
- Original mitt: MIT ÂĐ [Jason Miller](https://github.com/developit)
318
+ Original mitt: MIT (c) [Jason Miller](https://github.com/developit)
package/dist/index.d.ts CHANGED
@@ -15,3 +15,9 @@ export interface Emitter<Events extends Record<EventType, unknown>> {
15
15
  emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
16
16
  }
17
17
  export default function zephyrEvents<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): Emitter<Events>;
18
+ /**
19
+ * Creates a fast, non-snapshot event emitter. Handlers that modify the
20
+ * listener list during emit may cause skipped or double-fired handlers.
21
+ * Use when maximum throughput matters and handlers do not mutate listeners.
22
+ */
23
+ export declare function zephyrEventsFast<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): Emitter<Events>;
package/dist/index.js CHANGED
@@ -1,53 +1,154 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = zephyrEvents;
4
+ exports.zephyrEventsFast = zephyrEventsFast;
4
5
  function zephyrEvents(all) {
5
6
  all ??= new Map();
6
- const handlerSets = new Map();
7
- const getHandlerSet = (type) => {
8
- let handlers = handlerSets.get(type);
9
- if (!handlers) {
10
- handlers = new Set();
11
- handlerSets.set(type, handlers);
12
- all.set(type, []);
13
- }
14
- return handlers;
15
- };
16
- const syncMap = (type) => {
17
- const handlers = handlerSets.get(type);
18
- if (handlers) {
19
- all.set(type, [...handlers]);
7
+ let hasWildcards = false;
8
+ let emitting = 0;
9
+ return {
10
+ all,
11
+ on(type, handler) {
12
+ let handlers = all.get(type);
13
+ if (!handlers) {
14
+ handlers = [];
15
+ all.set(type, handlers);
16
+ }
17
+ handlers.push(handler);
18
+ if ('*' === type)
19
+ hasWildcards = true;
20
+ return () => {
21
+ const current = all.get(type);
22
+ if (current) {
23
+ const idx = current.indexOf(handler);
24
+ if (-1 < idx) {
25
+ current.splice(idx, 1);
26
+ }
27
+ if (0 === current.length) {
28
+ all.delete(type);
29
+ if ('*' === type)
30
+ hasWildcards = false;
31
+ }
32
+ }
33
+ };
34
+ },
35
+ off(type, handler) {
36
+ const handlers = all.get(type);
37
+ if (!handlers)
38
+ return;
39
+ if (handler) {
40
+ const idx = handlers.indexOf(handler);
41
+ if (-1 < idx) {
42
+ handlers.splice(idx, 1);
43
+ }
44
+ if (0 === handlers.length) {
45
+ all.delete(type);
46
+ if ('*' === type)
47
+ hasWildcards = false;
48
+ }
49
+ }
50
+ else {
51
+ all.delete(type);
52
+ if ('*' === type)
53
+ hasWildcards = false;
54
+ }
55
+ },
56
+ emit(type, evt) {
57
+ const handlers = all.get(type);
58
+ if (handlers) {
59
+ emitting++;
60
+ const snapshot = handlers.slice();
61
+ const len = snapshot.length;
62
+ for (let i = 0; i < len; i++) {
63
+ snapshot[i](evt);
64
+ }
65
+ emitting--;
66
+ }
67
+ if (hasWildcards && '*' !== type) {
68
+ const wildcards = all.get('*');
69
+ if (wildcards) {
70
+ emitting++;
71
+ const snapshot = wildcards.slice();
72
+ const len = snapshot.length;
73
+ for (let i = 0; i < len; i++) {
74
+ snapshot[i](type, evt);
75
+ }
76
+ emitting--;
77
+ }
78
+ }
20
79
  }
21
80
  };
81
+ }
82
+ /**
83
+ * Creates a fast, non-snapshot event emitter. Handlers that modify the
84
+ * listener list during emit may cause skipped or double-fired handlers.
85
+ * Use when maximum throughput matters and handlers do not mutate listeners.
86
+ */
87
+ function zephyrEventsFast(all) {
88
+ all ??= new Map();
89
+ let hasWildcards = false;
22
90
  return {
23
91
  all,
24
92
  on(type, handler) {
25
- const handlers = getHandlerSet(type);
26
- handlers.add(handler);
27
- syncMap(type);
93
+ let handlers = all.get(type);
94
+ if (!handlers) {
95
+ handlers = [];
96
+ all.set(type, handlers);
97
+ }
98
+ handlers.push(handler);
99
+ if ('*' === type)
100
+ hasWildcards = true;
28
101
  return () => {
29
- handlers.delete(handler);
30
- syncMap(type);
102
+ const current = all.get(type);
103
+ if (current) {
104
+ const idx = current.indexOf(handler);
105
+ if (-1 < idx) {
106
+ current.splice(idx, 1);
107
+ }
108
+ if (0 === current.length) {
109
+ all.delete(type);
110
+ if ('*' === type)
111
+ hasWildcards = false;
112
+ }
113
+ }
31
114
  };
32
115
  },
33
116
  off(type, handler) {
34
- const handlers = handlerSets.get(type);
117
+ const handlers = all.get(type);
35
118
  if (!handlers)
36
119
  return;
37
- handler ? handlers.delete(handler) : handlers.clear();
38
- syncMap(type);
120
+ if (handler) {
121
+ const idx = handlers.indexOf(handler);
122
+ if (-1 < idx) {
123
+ handlers.splice(idx, 1);
124
+ }
125
+ if (0 === handlers.length) {
126
+ all.delete(type);
127
+ if ('*' === type)
128
+ hasWildcards = false;
129
+ }
130
+ }
131
+ else {
132
+ all.delete(type);
133
+ if ('*' === type)
134
+ hasWildcards = false;
135
+ }
39
136
  },
40
137
  emit(type, evt) {
41
- const handlers = handlerSets.get(type);
42
- if (handlers?.size) {
43
- for (const handler of [...handlers]) {
44
- handler(evt);
138
+ const handlers = all.get(type);
139
+ if (handlers) {
140
+ const len = handlers.length;
141
+ for (let i = 0; i < len; i++) {
142
+ handlers[i](evt);
45
143
  }
46
144
  }
47
- const wildcards = handlerSets.get('*');
48
- if (wildcards?.size) {
49
- for (const handler of [...wildcards]) {
50
- handler(type, evt);
145
+ if (hasWildcards && '*' !== type) {
146
+ const wildcards = all.get('*');
147
+ if (wildcards) {
148
+ const len = wildcards.length;
149
+ for (let i = 0; i < len; i++) {
150
+ wildcards[i](type, evt);
151
+ }
51
152
  }
52
153
  }
53
154
  }
@@ -1 +1 @@
1
- {"root":["../src/index.ts"],"version":"5.9.2"}
1
+ {"root":["../src/index.ts"],"version":"5.9.3"}
@@ -1 +1,149 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value: true});exports.default=zephyrEvents;function zephyrEvents(all){all ??=new Map();const handlerSets=new Map();const getHandlerSet=(type)=>{let handlers=handlerSets.get(type);if(!handlers){handlers=new Set();handlerSets.set(type,handlers);all.set(type,[]);}return handlers;};const syncMap=(type)=>{const handlers=handlerSets.get(type);if(handlers){all.set(type,[...handlers]);}};return{all,on(type,handler){const handlers=getHandlerSet(type);handlers.add(handler);syncMap(type);return()=>{handlers.delete(handler);syncMap(type);};},off(type,handler){const handlers=handlerSets.get(type);if(!handlers)return;handler ? handlers.delete(handler): handlers.clear();syncMap(type);},emit(type,evt){const handlers=handlerSets.get(type);if(handlers?.size){for(const handler of [...handlers]){handler(evt);}}const wildcards=handlerSets.get('*');if(wildcards?.size){for(const handler of [...wildcards]){handler(type,evt);}}}};}
1
+ "use strict";
2
+ module.exports = function zephyrEvents(all) {
3
+ all ??= new Map();
4
+ let hasWildcards = false;
5
+ let emitting = 0;
6
+ return {
7
+ all,
8
+ on(type, handler) {
9
+ let handlers = all.get(type);
10
+ if (!handlers) {
11
+ handlers = [];
12
+ all.set(type, handlers);
13
+ }
14
+ handlers.push(handler);
15
+ if ('*' === type)
16
+ hasWildcards = true;
17
+ return () => {
18
+ const current = all.get(type);
19
+ if (current) {
20
+ const idx = current.indexOf(handler);
21
+ if (-1 < idx) {
22
+ current.splice(idx, 1);
23
+ }
24
+ if (0 === current.length) {
25
+ all.delete(type);
26
+ if ('*' === type)
27
+ hasWildcards = false;
28
+ }
29
+ }
30
+ };
31
+ },
32
+ off(type, handler) {
33
+ const handlers = all.get(type);
34
+ if (!handlers)
35
+ return;
36
+ if (handler) {
37
+ const idx = handlers.indexOf(handler);
38
+ if (-1 < idx) {
39
+ handlers.splice(idx, 1);
40
+ }
41
+ if (0 === handlers.length) {
42
+ all.delete(type);
43
+ if ('*' === type)
44
+ hasWildcards = false;
45
+ }
46
+ }
47
+ else {
48
+ all.delete(type);
49
+ if ('*' === type)
50
+ hasWildcards = false;
51
+ }
52
+ },
53
+ emit(type, evt) {
54
+ const handlers = all.get(type);
55
+ if (handlers) {
56
+ emitting++;
57
+ const snapshot = handlers.slice();
58
+ const len = snapshot.length;
59
+ for (let i = 0; i < len; i++) {
60
+ snapshot[i](evt);
61
+ }
62
+ emitting--;
63
+ }
64
+ if (hasWildcards && '*' !== type) {
65
+ const wildcards = all.get('*');
66
+ if (wildcards) {
67
+ emitting++;
68
+ const snapshot = wildcards.slice();
69
+ const len = snapshot.length;
70
+ for (let i = 0; i < len; i++) {
71
+ snapshot[i](type, evt);
72
+ }
73
+ emitting--;
74
+ }
75
+ }
76
+ }
77
+ };
78
+ }
79
+ module.exports.default = module.exports;
80
+ module.exports.zephyrEventsFast = function zephyrEventsFast(all) {
81
+ all ??= new Map();
82
+ let hasWildcards = false;
83
+ return {
84
+ all,
85
+ on(type, handler) {
86
+ let handlers = all.get(type);
87
+ if (!handlers) {
88
+ handlers = [];
89
+ all.set(type, handlers);
90
+ }
91
+ handlers.push(handler);
92
+ if ('*' === type)
93
+ hasWildcards = true;
94
+ return () => {
95
+ const current = all.get(type);
96
+ if (current) {
97
+ const idx = current.indexOf(handler);
98
+ if (-1 < idx) {
99
+ current.splice(idx, 1);
100
+ }
101
+ if (0 === current.length) {
102
+ all.delete(type);
103
+ if ('*' === type)
104
+ hasWildcards = false;
105
+ }
106
+ }
107
+ };
108
+ },
109
+ off(type, handler) {
110
+ const handlers = all.get(type);
111
+ if (!handlers)
112
+ return;
113
+ if (handler) {
114
+ const idx = handlers.indexOf(handler);
115
+ if (-1 < idx) {
116
+ handlers.splice(idx, 1);
117
+ }
118
+ if (0 === handlers.length) {
119
+ all.delete(type);
120
+ if ('*' === type)
121
+ hasWildcards = false;
122
+ }
123
+ }
124
+ else {
125
+ all.delete(type);
126
+ if ('*' === type)
127
+ hasWildcards = false;
128
+ }
129
+ },
130
+ emit(type, evt) {
131
+ const handlers = all.get(type);
132
+ if (handlers) {
133
+ const len = handlers.length;
134
+ for (let i = 0; i < len; i++) {
135
+ handlers[i](evt);
136
+ }
137
+ }
138
+ if (hasWildcards && '*' !== type) {
139
+ const wildcards = all.get('*');
140
+ if (wildcards) {
141
+ const len = wildcards.length;
142
+ for (let i = 0; i < len; i++) {
143
+ wildcards[i](type, evt);
144
+ }
145
+ }
146
+ }
147
+ }
148
+ };
149
+ }
@@ -1 +1,147 @@
1
- export default zephyrEvents;function zephyrEvents(all){all ??=new Map();const handlerSets=new Map();const getHandlerSet=(type)=>{let handlers=handlerSets.get(type);if(!handlers){handlers=new Set();handlerSets.set(type,handlers);all.set(type,[]);}return handlers;};const syncMap=(type)=>{const handlers=handlerSets.get(type);if(handlers){all.set(type,[...handlers]);}};return{all,on(type,handler){const handlers=getHandlerSet(type);handlers.add(handler);syncMap(type);return()=>{handlers.delete(handler);syncMap(type);};},off(type,handler){const handlers=handlerSets.get(type);if(!handlers)return;handler ? handlers.delete(handler): handlers.clear();syncMap(type);},emit(type,evt){const handlers=handlerSets.get(type);if(handlers?.size){for(const handler of [...handlers]){handler(evt);}}const wildcards=handlerSets.get('*');if(wildcards?.size){for(const handler of [...wildcards]){handler(type,evt);}}}};}
1
+ export default function zephyrEvents(all) {
2
+ all ??= new Map();
3
+ let hasWildcards = false;
4
+ let emitting = 0;
5
+ return {
6
+ all,
7
+ on(type, handler) {
8
+ let handlers = all.get(type);
9
+ if (!handlers) {
10
+ handlers = [];
11
+ all.set(type, handlers);
12
+ }
13
+ handlers.push(handler);
14
+ if ('*' === type)
15
+ hasWildcards = true;
16
+ return () => {
17
+ const current = all.get(type);
18
+ if (current) {
19
+ const idx = current.indexOf(handler);
20
+ if (-1 < idx) {
21
+ current.splice(idx, 1);
22
+ }
23
+ if (0 === current.length) {
24
+ all.delete(type);
25
+ if ('*' === type)
26
+ hasWildcards = false;
27
+ }
28
+ }
29
+ };
30
+ },
31
+ off(type, handler) {
32
+ const handlers = all.get(type);
33
+ if (!handlers)
34
+ return;
35
+ if (handler) {
36
+ const idx = handlers.indexOf(handler);
37
+ if (-1 < idx) {
38
+ handlers.splice(idx, 1);
39
+ }
40
+ if (0 === handlers.length) {
41
+ all.delete(type);
42
+ if ('*' === type)
43
+ hasWildcards = false;
44
+ }
45
+ }
46
+ else {
47
+ all.delete(type);
48
+ if ('*' === type)
49
+ hasWildcards = false;
50
+ }
51
+ },
52
+ emit(type, evt) {
53
+ const handlers = all.get(type);
54
+ if (handlers) {
55
+ emitting++;
56
+ const snapshot = handlers.slice();
57
+ const len = snapshot.length;
58
+ for (let i = 0; i < len; i++) {
59
+ snapshot[i](evt);
60
+ }
61
+ emitting--;
62
+ }
63
+ if (hasWildcards && '*' !== type) {
64
+ const wildcards = all.get('*');
65
+ if (wildcards) {
66
+ emitting++;
67
+ const snapshot = wildcards.slice();
68
+ const len = snapshot.length;
69
+ for (let i = 0; i < len; i++) {
70
+ snapshot[i](type, evt);
71
+ }
72
+ emitting--;
73
+ }
74
+ }
75
+ }
76
+ };
77
+ }
78
+ export function zephyrEventsFast(all) {
79
+ all ??= new Map();
80
+ let hasWildcards = false;
81
+ return {
82
+ all,
83
+ on(type, handler) {
84
+ let handlers = all.get(type);
85
+ if (!handlers) {
86
+ handlers = [];
87
+ all.set(type, handlers);
88
+ }
89
+ handlers.push(handler);
90
+ if ('*' === type)
91
+ hasWildcards = true;
92
+ return () => {
93
+ const current = all.get(type);
94
+ if (current) {
95
+ const idx = current.indexOf(handler);
96
+ if (-1 < idx) {
97
+ current.splice(idx, 1);
98
+ }
99
+ if (0 === current.length) {
100
+ all.delete(type);
101
+ if ('*' === type)
102
+ hasWildcards = false;
103
+ }
104
+ }
105
+ };
106
+ },
107
+ off(type, handler) {
108
+ const handlers = all.get(type);
109
+ if (!handlers)
110
+ return;
111
+ if (handler) {
112
+ const idx = handlers.indexOf(handler);
113
+ if (-1 < idx) {
114
+ handlers.splice(idx, 1);
115
+ }
116
+ if (0 === handlers.length) {
117
+ all.delete(type);
118
+ if ('*' === type)
119
+ hasWildcards = false;
120
+ }
121
+ }
122
+ else {
123
+ all.delete(type);
124
+ if ('*' === type)
125
+ hasWildcards = false;
126
+ }
127
+ },
128
+ emit(type, evt) {
129
+ const handlers = all.get(type);
130
+ if (handlers) {
131
+ const len = handlers.length;
132
+ for (let i = 0; i < len; i++) {
133
+ handlers[i](evt);
134
+ }
135
+ }
136
+ if (hasWildcards && '*' !== type) {
137
+ const wildcards = all.get('*');
138
+ if (wildcards) {
139
+ const len = wildcards.length;
140
+ for (let i = 0; i < len; i++) {
141
+ wildcards[i](type, evt);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ };
147
+ }
@@ -1,7 +1,156 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global = global || self, global.zephyrEvents = factory());
5
- }(this, (function () { 'use strict';
6
- "use strict";Object.defineProperty(exports,"__esModule",{value: true});exports.default=zephyrEvents;function zephyrEvents(all){all ??=new Map();const handlerSets=new Map();const getHandlerSet=(type)=>{let handlers=handlerSets.get(type);if(!handlers){handlers=new Set();handlerSets.set(type,handlers);all.set(type,[]);}return handlers;};const syncMap=(type)=>{const handlers=handlerSets.get(type);if(handlers){all.set(type,[...handlers]);}};return{all,on(type,handler){const handlers=getHandlerSet(type);handlers.add(handler);syncMap(type);return()=>{handlers.delete(handler);syncMap(type);};},off(type,handler){const handlers=handlerSets.get(type);if(!handlers)return;handler ? handlers.delete(handler): handlers.clear();syncMap(type);},emit(type,evt){const handlers=handlerSets.get(type);if(handlers?.size){for(const handler of [...handlers]){handler(evt);}}const wildcards=handlerSets.get('*');if(wildcards?.size){for(const handler of [...wildcards]){handler(type,evt);}}}};}
7
- })));
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = global || self, factory(global));
5
+ }(this, (function (exports) {
6
+ 'use strict';
7
+ function zephyrEvents(all) {
8
+ all ??= new Map();
9
+ let hasWildcards = false;
10
+ let emitting = 0;
11
+ return {
12
+ all,
13
+ on(type, handler) {
14
+ let handlers = all.get(type);
15
+ if (!handlers) {
16
+ handlers = [];
17
+ all.set(type, handlers);
18
+ }
19
+ handlers.push(handler);
20
+ if ('*' === type)
21
+ hasWildcards = true;
22
+ return () => {
23
+ const current = all.get(type);
24
+ if (current) {
25
+ const idx = current.indexOf(handler);
26
+ if (-1 < idx) {
27
+ current.splice(idx, 1);
28
+ }
29
+ if (0 === current.length) {
30
+ all.delete(type);
31
+ if ('*' === type)
32
+ hasWildcards = false;
33
+ }
34
+ }
35
+ };
36
+ },
37
+ off(type, handler) {
38
+ const handlers = all.get(type);
39
+ if (!handlers)
40
+ return;
41
+ if (handler) {
42
+ const idx = handlers.indexOf(handler);
43
+ if (-1 < idx) {
44
+ handlers.splice(idx, 1);
45
+ }
46
+ if (0 === handlers.length) {
47
+ all.delete(type);
48
+ if ('*' === type)
49
+ hasWildcards = false;
50
+ }
51
+ }
52
+ else {
53
+ all.delete(type);
54
+ if ('*' === type)
55
+ hasWildcards = false;
56
+ }
57
+ },
58
+ emit(type, evt) {
59
+ const handlers = all.get(type);
60
+ if (handlers) {
61
+ emitting++;
62
+ const snapshot = handlers.slice();
63
+ const len = snapshot.length;
64
+ for (let i = 0; i < len; i++) {
65
+ snapshot[i](evt);
66
+ }
67
+ emitting--;
68
+ }
69
+ if (hasWildcards && '*' !== type) {
70
+ const wildcards = all.get('*');
71
+ if (wildcards) {
72
+ emitting++;
73
+ const snapshot = wildcards.slice();
74
+ const len = snapshot.length;
75
+ for (let i = 0; i < len; i++) {
76
+ snapshot[i](type, evt);
77
+ }
78
+ emitting--;
79
+ }
80
+ }
81
+ }
82
+ };
83
+ }
84
+ function zephyrEventsFast(all) {
85
+ all ??= new Map();
86
+ let hasWildcards = false;
87
+ return {
88
+ all,
89
+ on(type, handler) {
90
+ let handlers = all.get(type);
91
+ if (!handlers) {
92
+ handlers = [];
93
+ all.set(type, handlers);
94
+ }
95
+ handlers.push(handler);
96
+ if ('*' === type)
97
+ hasWildcards = true;
98
+ return () => {
99
+ const current = all.get(type);
100
+ if (current) {
101
+ const idx = current.indexOf(handler);
102
+ if (-1 < idx) {
103
+ current.splice(idx, 1);
104
+ }
105
+ if (0 === current.length) {
106
+ all.delete(type);
107
+ if ('*' === type)
108
+ hasWildcards = false;
109
+ }
110
+ }
111
+ };
112
+ },
113
+ off(type, handler) {
114
+ const handlers = all.get(type);
115
+ if (!handlers)
116
+ return;
117
+ if (handler) {
118
+ const idx = handlers.indexOf(handler);
119
+ if (-1 < idx) {
120
+ handlers.splice(idx, 1);
121
+ }
122
+ if (0 === handlers.length) {
123
+ all.delete(type);
124
+ if ('*' === type)
125
+ hasWildcards = false;
126
+ }
127
+ }
128
+ else {
129
+ all.delete(type);
130
+ if ('*' === type)
131
+ hasWildcards = false;
132
+ }
133
+ },
134
+ emit(type, evt) {
135
+ const handlers = all.get(type);
136
+ if (handlers) {
137
+ const len = handlers.length;
138
+ for (let i = 0; i < len; i++) {
139
+ handlers[i](evt);
140
+ }
141
+ }
142
+ if (hasWildcards && '*' !== type) {
143
+ const wildcards = all.get('*');
144
+ if (wildcards) {
145
+ const len = wildcards.length;
146
+ for (let i = 0; i < len; i++) {
147
+ wildcards[i](type, evt);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ };
153
+ }
154
+ exports.zephyrEvents = zephyrEvents;
155
+ exports.zephyrEventsFast = zephyrEventsFast;
156
+ })));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zephyr-events",
3
- "version": "1.1.2",
4
- "description": "Ultra-fast ES2023 event emitter with 889B bundle size and race-condition safety",
3
+ "version": "1.3.0",
4
+ "description": "Tiny typed event emitter with race-condition safety",
5
5
  "main": "dist/zephyr-events.js",
6
6
  "module": "dist/zephyr-events.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -19,23 +19,29 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "build": "tsc --build && node build.js",
22
- "prepublishOnly": "npm run build"
22
+ "test": "node test/index.js",
23
+ "prepublishOnly": "npm run build && npm test"
23
24
  },
24
25
  "repository": {
25
26
  "type": "git",
26
27
  "url": "git+https://github.com/ebogdum/zephyr-events.git"
27
28
  },
28
29
  "keywords": [
29
- "events",
30
+ "event-emitter",
30
31
  "eventemitter",
32
+ "events",
31
33
  "emitter",
32
34
  "pubsub",
35
+ "event-bus",
33
36
  "typescript",
34
- "es2023",
35
- "fast",
37
+ "typed-events",
38
+ "mitt-alternative",
39
+ "lightweight",
36
40
  "tiny",
37
- "performance",
38
- "race-condition-safe"
41
+ "subscribe",
42
+ "observer",
43
+ "race-condition-safe",
44
+ "wildcard"
39
45
  ],
40
46
  "homepage": "https://github.com/ebogdum/zephyr-events",
41
47
  "bugs": {
@@ -44,7 +50,7 @@
44
50
  "author": "ebogdum <https://github.com/ebogdum>",
45
51
  "license": "MIT",
46
52
  "engines": {
47
- "node": ">=16"
53
+ "node": ">=18"
48
54
  },
49
55
  "devDependencies": {
50
56
  "typescript": "^5.9.2"