@d1g1tal/subscribr 4.0.3 โ 4.1.8
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 +401 -43
- package/dist/subscribr.d.ts +35 -7
- package/dist/subscribr.js +256 -1
- package/dist/subscribr.js.map +4 -4
- package/package.json +71 -74
- package/src/@types/index.ts +11 -2
- package/src/context-event-handler.ts +6 -6
- package/src/index.ts +1 -1
- package/src/subscribr.ts +63 -7
package/README.md
CHANGED
|
@@ -1,75 +1,433 @@
|
|
|
1
1
|
# subscribr
|
|
2
|
-
JavaScript Publish/Subscribe
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
[](https://github.com/D1g1talEntr0py/subscribr/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/D1g1talEntr0py/subscribr)
|
|
5
|
+
[](https://www.npmjs.com/package/@d1g1tal/subscribr)
|
|
6
|
+
[](https://opensource.org/licenses/ISC)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
A lightweight TypeScript publish/subscribe library implementing the Observer pattern.
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- ๐ฏ **Simple API** - Subscribe, publish, and unsubscribe with ease
|
|
14
|
+
- ๐ **Type-safe** - Full TypeScript support with strict typing
|
|
15
|
+
- ๐ช **Context binding** - Maintain proper `this` context in handlers
|
|
16
|
+
- ๐ **Once subscriptions** - Auto-unsubscribe after first event via options
|
|
17
|
+
- ๐ก๏ธ **Error handling** - Built-in error boundary prevents cascading failures
|
|
18
|
+
- โ
**Validation** - Event name validation for reliable behavior
|
|
19
|
+
- ๐ฆ **Lightweight** - Minimal dependencies (@d1g1tal/collections)
|
|
20
|
+
- ๐งช **Well-tested** - Comprehensive test coverage
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
9
23
|
|
|
10
|
-
### Installation:
|
|
11
24
|
```bash
|
|
12
25
|
# Install with pnpm
|
|
13
|
-
pnpm
|
|
26
|
+
pnpm add @d1g1tal/subscribr
|
|
14
27
|
|
|
15
|
-
# Install with
|
|
28
|
+
# Install with npm
|
|
16
29
|
npm install @d1g1tal/subscribr
|
|
17
|
-
```
|
|
18
|
-
Or Script tags from downloaded script or from a NPM CDN like jsDelivr
|
|
19
|
-
|
|
20
|
-
```html
|
|
21
|
-
<!-- Load as global script -->
|
|
22
|
-
<script src="/subscribr/dist/iife/subscribr.min.js"></script>
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
# Install with yarn
|
|
32
|
+
yarn add @d1g1tal/subscribr
|
|
33
|
+
```
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
<script type="module">
|
|
29
|
-
import Subscribr from '/app/js/subscribr.min.js';
|
|
30
|
-
</script>
|
|
35
|
+
### CDN Usage
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
```html
|
|
38
|
+
<!-- Load as ES Module from CDN -->
|
|
33
39
|
<script type="module">
|
|
34
|
-
|
|
40
|
+
import Subscribr from 'https://cdn.jsdelivr.net/npm/@d1g1tal/subscribr@4/dist/subscribr.js';
|
|
35
41
|
</script>
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
39
46
|
```javascript
|
|
40
|
-
// ES Module
|
|
41
47
|
import Subscribr from '@d1g1tal/subscribr';
|
|
42
48
|
|
|
49
|
+
// Create a new instance
|
|
50
|
+
const subscribr = new Subscribr();
|
|
51
|
+
|
|
52
|
+
// Subscribe to an event
|
|
53
|
+
const subscription = subscribr.subscribe('user-login', (event, data) => {
|
|
54
|
+
console.log('User logged in:', data.userId);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Publish an event
|
|
58
|
+
subscribr.publish('user-login', undefined, { userId: 123 });
|
|
59
|
+
// Output: User logged in: 123
|
|
60
|
+
|
|
61
|
+
// Check if subscribed
|
|
62
|
+
console.log(subscribr.isSubscribed(subscription)); // true
|
|
63
|
+
|
|
64
|
+
// Unsubscribe
|
|
65
|
+
subscribr.unsubscribe(subscription);
|
|
66
|
+
console.log(subscribr.isSubscribed(subscription)); // false
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API Reference
|
|
70
|
+
|
|
71
|
+
### Constructor
|
|
72
|
+
|
|
73
|
+
#### `new Subscribr()`
|
|
74
|
+
|
|
75
|
+
Creates a new Subscribr instance.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const subscribr = new Subscribr();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### Methods
|
|
84
|
+
|
|
85
|
+
#### `subscribe(eventName, eventHandler, context?, options?)`
|
|
86
|
+
|
|
87
|
+
Subscribe to an event.
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
- `eventName` (string) - The name of the event to subscribe to
|
|
91
|
+
- `eventHandler` (function) - The handler function `(event: Event, data?: unknown) => void`
|
|
92
|
+
- `context` (any, optional) - The `this` context for the handler (defaults to the handler itself)
|
|
93
|
+
- `options` (object, optional) - Subscription options
|
|
94
|
+
- `once` (boolean) - If true, automatically unsubscribe after first event
|
|
95
|
+
|
|
96
|
+
**Returns:** `Subscription` - A subscription object for managing the subscription
|
|
97
|
+
|
|
98
|
+
**Throws:**
|
|
99
|
+
- `TypeError` - If event name is not a non-empty string
|
|
100
|
+
- `Error` - If event name has leading or trailing whitespace
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Basic subscription
|
|
104
|
+
const subscription = subscribr.subscribe('my-event', (event, data) => {
|
|
105
|
+
console.log('Event received:', event.type, data);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// With custom context
|
|
109
|
+
const myObject = {
|
|
110
|
+
name: 'MyObject',
|
|
111
|
+
handleEvent(event, data) {
|
|
112
|
+
console.log(this.name, 'received:', data);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
subscribr.subscribe('my-event', myObject.handleEvent, myObject);
|
|
116
|
+
|
|
117
|
+
// One-time subscription using options
|
|
118
|
+
subscribr.subscribe('init', (event, data) => {
|
|
119
|
+
console.log('Initialization complete:', data);
|
|
120
|
+
}, undefined, { once: true });
|
|
121
|
+
|
|
122
|
+
subscribr.publish('init', undefined, { status: 'ready' });
|
|
123
|
+
// Output: Initialization complete: { status: 'ready' }
|
|
124
|
+
|
|
125
|
+
subscribr.publish('init', undefined, { status: 'ready' });
|
|
126
|
+
// No output - handler was auto-unsubscribed
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
#### `publish(eventName, event?, data?)`
|
|
132
|
+
|
|
133
|
+
Publish an event to all subscribed handlers.
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
- `eventName` (string) - The name of the event to publish
|
|
137
|
+
- `event` (Event, optional) - A custom event object (defaults to `new CustomEvent(eventName)`)
|
|
138
|
+
- `data` (any, optional) - Data to pass to event handlers
|
|
139
|
+
|
|
140
|
+
**Returns:** `void`
|
|
141
|
+
|
|
142
|
+
**Throws:**
|
|
143
|
+
- `TypeError` - If event name is not a non-empty string
|
|
144
|
+
- `Error` - If event name has leading or trailing whitespace
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// Publish with just event name and data
|
|
148
|
+
subscribr.publish('user-action', undefined, { action: 'click', target: 'button' });
|
|
149
|
+
|
|
150
|
+
// Publish with custom event
|
|
151
|
+
const customEvent = new CustomEvent('custom-event', { detail: 'info' });
|
|
152
|
+
subscribr.publish('custom-event', customEvent, { extra: 'data' });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
#### `unsubscribe(subscription)`
|
|
158
|
+
|
|
159
|
+
Unsubscribe from an event.
|
|
160
|
+
|
|
161
|
+
**Parameters:**
|
|
162
|
+
- `subscription` (Subscription) - The subscription object returned from `subscribe()` or `once()`
|
|
163
|
+
|
|
164
|
+
**Returns:** `boolean` - `true` if successfully unsubscribed, `false` if subscription wasn't found
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const subscription = subscribr.subscribe('my-event', handler);
|
|
168
|
+
const result = subscribr.unsubscribe(subscription); // true
|
|
169
|
+
const result2 = subscribr.unsubscribe(subscription); // false (already unsubscribed)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
#### `isSubscribed(subscription)`
|
|
175
|
+
|
|
176
|
+
Check if a subscription is still active.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
- `subscription` (Subscription) - The subscription object to check
|
|
180
|
+
|
|
181
|
+
**Returns:** `boolean` - `true` if the subscription is active, `false` otherwise
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const subscription = subscribr.subscribe('my-event', handler);
|
|
185
|
+
subscribr.isSubscribed(subscription); // true
|
|
186
|
+
subscribr.unsubscribe(subscription);
|
|
187
|
+
subscribr.isSubscribed(subscription); // false
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
#### `setErrorHandler(errorHandler)`
|
|
193
|
+
|
|
194
|
+
Set a custom error handler for handling errors that occur in event listeners. By default, errors are logged to the console.
|
|
195
|
+
|
|
196
|
+
**Parameters:**
|
|
197
|
+
- `errorHandler` (function) - The error handler function `(error: Error, eventName: string, event: Event, data?: unknown) => void`
|
|
198
|
+
|
|
199
|
+
**Returns:** `void`
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Set a custom error handler
|
|
203
|
+
subscribr.setErrorHandler((error, eventName, event, data) => {
|
|
204
|
+
console.error(`Error in handler for '${eventName}':`, error.message);
|
|
205
|
+
// Send to error tracking service
|
|
206
|
+
errorTracker.report(error, { eventName, data });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Now if a handler throws an error, it won't break other handlers
|
|
210
|
+
subscribr.subscribe('my-event', () => {
|
|
211
|
+
throw new Error('Oops!');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
subscribr.subscribe('my-event', () => {
|
|
215
|
+
console.log('This still runs!'); // โ
Executes despite the error above
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
subscribr.publish('my-event');
|
|
219
|
+
// Output: Error in handler for 'my-event': Oops!
|
|
220
|
+
// Output: This still runs!
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Usage Examples
|
|
226
|
+
|
|
227
|
+
### Multiple Handlers for the Same Event
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
const subscribr = new Subscribr();
|
|
231
|
+
|
|
232
|
+
subscribr.subscribe('data-update', (event, data) => {
|
|
233
|
+
console.log('Handler 1:', data);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
subscribr.subscribe('data-update', (event, data) => {
|
|
237
|
+
console.log('Handler 2:', data);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
subscribr.publish('data-update', undefined, { value: 42 });
|
|
241
|
+
// Output:
|
|
242
|
+
// Handler 1: { value: 42 }
|
|
243
|
+
// Handler 2: { value: 42 }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Using with DOM Events
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
const subscribr = new Subscribr();
|
|
250
|
+
|
|
251
|
+
subscribr.subscribe('user-click', (event, data) => {
|
|
252
|
+
console.log('User clicked:', data.elementId);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
document.getElementById('myButton').addEventListener('click', (event) => {
|
|
256
|
+
subscribr.publish('user-click', event, { elementId: event.target.id });
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Class-based Event Handling
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
class UserManager {
|
|
264
|
+
constructor(subscribr) {
|
|
265
|
+
this.subscribr = subscribr;
|
|
266
|
+
this.users = [];
|
|
267
|
+
|
|
268
|
+
// Subscribe with proper context binding
|
|
269
|
+
this.subscribr.subscribe('user-added', this.onUserAdded, this);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
onUserAdded(event, data) {
|
|
273
|
+
// 'this' correctly refers to UserManager instance
|
|
274
|
+
this.users.push(data.user);
|
|
275
|
+
console.log(`Total users: ${this.users.length}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
addUser(user) {
|
|
279
|
+
this.subscribr.publish('user-added', undefined, { user });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const subscribr = new Subscribr();
|
|
284
|
+
const manager = new UserManager(subscribr);
|
|
285
|
+
manager.addUser({ id: 1, name: 'Alice' });
|
|
286
|
+
// Output: Total users: 1
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Error-Resistant Event System
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
43
292
|
const subscribr = new Subscribr();
|
|
44
|
-
const eventName = 'myEvent';
|
|
45
293
|
|
|
46
|
-
//
|
|
47
|
-
|
|
294
|
+
// Set up error tracking
|
|
295
|
+
subscribr.setErrorHandler((error, eventName, event, data) => {
|
|
296
|
+
console.error(`Error in handler for '${eventName}':`, error.message);
|
|
297
|
+
// Send to error tracking service (if available)
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Even if one handler fails, others continue
|
|
301
|
+
subscribr.subscribe('process-data', (event, data) => {
|
|
302
|
+
if (!data.valid) throw new Error('Invalid data');
|
|
303
|
+
processData(data);
|
|
304
|
+
});
|
|
48
305
|
|
|
49
|
-
|
|
50
|
-
|
|
306
|
+
subscribr.subscribe('process-data', (event, data) => {
|
|
307
|
+
// This still executes even if the previous handler throws
|
|
308
|
+
updateUI(data);
|
|
309
|
+
});
|
|
310
|
+
```
|
|
51
311
|
|
|
52
|
-
|
|
53
|
-
const isSubscribed = subscribr.isSubscribed(mySubscription); // true
|
|
312
|
+
### One-Time Subscriptions
|
|
54
313
|
|
|
55
|
-
|
|
314
|
+
```javascript
|
|
315
|
+
const subscribr = new Subscribr();
|
|
56
316
|
|
|
57
|
-
|
|
317
|
+
// One-time subscription using options
|
|
318
|
+
subscribr.subscribe('app-ready', (event, data) => {
|
|
319
|
+
console.log('App initialized with config:', data.config);
|
|
320
|
+
startApplication(data.config);
|
|
321
|
+
}, undefined, { once: true });
|
|
58
322
|
|
|
323
|
+
// This will only trigger the handler once
|
|
324
|
+
subscribr.publish('app-ready', undefined, { config: { theme: 'dark' } });
|
|
325
|
+
subscribr.publish('app-ready', undefined, { config: { theme: 'light' } }); // Ignored
|
|
326
|
+
```
|
|
59
327
|
|
|
60
|
-
|
|
61
|
-
const eventTargetSubscription = subsribr.subscribe('eventTargetChanged', (event, data) => console.log(`Event target changed with data: ${data}`));
|
|
328
|
+
## TypeScript Support
|
|
62
329
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
330
|
+
Full TypeScript support with strict typing:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import Subscribr from '@d1g1tal/subscribr';
|
|
334
|
+
|
|
335
|
+
interface UserData {
|
|
336
|
+
userId: number;
|
|
337
|
+
userName: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const subscribr = new Subscribr();
|
|
341
|
+
|
|
342
|
+
subscribr.subscribe('user-login', (event: Event, data?: UserData) => {
|
|
343
|
+
if (data) {
|
|
344
|
+
console.log(`User ${data.userName} (ID: ${data.userId}) logged in`);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
subscribr.publish<UserData>('user-login', undefined, {
|
|
349
|
+
userId: 123,
|
|
350
|
+
userName: 'Alice'
|
|
67
351
|
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Event Name Validation
|
|
355
|
+
|
|
356
|
+
Event names are validated to prevent common mistakes:
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// โ These will throw errors:
|
|
360
|
+
subscribr.subscribe('', handler); // TypeError: Event name must be a non-empty string
|
|
361
|
+
subscribr.subscribe(' event', handler); // Error: Event name cannot have leading or trailing whitespace
|
|
362
|
+
subscribr.subscribe('event ', handler); // Error: Event name cannot have leading or trailing whitespace
|
|
363
|
+
subscribr.subscribe(null, handler); // TypeError: Event name must be a non-empty string
|
|
364
|
+
|
|
365
|
+
// โ
These are valid:
|
|
366
|
+
subscribr.subscribe('my-event', handler);
|
|
367
|
+
subscribr.subscribe('user:login', handler);
|
|
368
|
+
subscribr.subscribe('data_update', handler);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Best Practices
|
|
372
|
+
|
|
373
|
+
1. **Always unsubscribe** when done to prevent memory leaks:
|
|
374
|
+
```javascript
|
|
375
|
+
const subscription = subscribr.subscribe('event', handler);
|
|
376
|
+
// ... later
|
|
377
|
+
subscribr.unsubscribe(subscription);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
2. **Use `{ once: true }` option for one-time events** to avoid manual cleanup:
|
|
381
|
+
```javascript
|
|
382
|
+
subscribr.subscribe('init', handler, undefined, { once: true }); // Auto-unsubscribes
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
3. **Set up error handling** for production applications:
|
|
386
|
+
```javascript
|
|
387
|
+
subscribr.setErrorHandler((error, eventName) => {
|
|
388
|
+
console.error(`Event '${eventName}' error:`, error);
|
|
389
|
+
// Report to error tracking service if available
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
4. **Use meaningful event names** with consistent naming conventions:
|
|
394
|
+
```javascript
|
|
395
|
+
// Good
|
|
396
|
+
subscribr.subscribe('user-login', handler);
|
|
397
|
+
subscribr.subscribe('data-updated', handler);
|
|
398
|
+
|
|
399
|
+
// Avoid
|
|
400
|
+
subscribr.subscribe('event1', handler);
|
|
401
|
+
subscribr.subscribe('stuff', handler);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
5. **Bind context properly** for class methods:
|
|
405
|
+
```javascript
|
|
406
|
+
class MyClass {
|
|
407
|
+
constructor() {
|
|
408
|
+
subscribr.subscribe('event', this.handleEvent, this);
|
|
409
|
+
}
|
|
410
|
+
handleEvent(event, data) {
|
|
411
|
+
// 'this' refers to MyClass instance
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Browser Support
|
|
417
|
+
|
|
418
|
+
Supports all modern browsers with ES6 module support and Node.js โฅ 20.15.1
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
ISC
|
|
423
|
+
|
|
424
|
+
## Contributing
|
|
68
425
|
|
|
69
|
-
|
|
70
|
-
const isSubscribed = subscribr.isSubscribed(eventTargetSubscription); // true
|
|
426
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
71
427
|
|
|
72
|
-
|
|
428
|
+
## Links
|
|
73
429
|
|
|
74
|
-
|
|
75
|
-
|
|
430
|
+
- [GitHub Repository](https://github.com/D1g1talEntr0py/subscribr)
|
|
431
|
+
- [npm Package](https://www.npmjs.com/package/@d1g1tal/subscribr)
|
|
432
|
+
- [Issue Tracker](https://github.com/D1g1talEntr0py/subscribr/issues)
|
|
433
|
+
- [Changelog](./CHANGELOG.md)
|
package/dist/subscribr.d.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
/** Context event listener function. */
|
|
2
|
-
type
|
|
2
|
+
type EventHandler = (event: Event, data?: unknown) => void;
|
|
3
|
+
/** Error handler function for handling errors in event listeners. */
|
|
4
|
+
type ErrorHandler = (error: Error, eventName: string, event: Event, data?: unknown) => void;
|
|
5
|
+
/** Subscription options. */
|
|
6
|
+
interface SubscriptionOptions {
|
|
7
|
+
/** If true, the subscription will automatically unsubscribe after the first event. */
|
|
8
|
+
once?: boolean;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
/** A wrapper for an event handler that binds a context to the event handler. */
|
|
5
12
|
declare class ContextEventHandler {
|
|
6
13
|
private readonly context;
|
|
7
|
-
private readonly
|
|
14
|
+
private readonly eventHandler;
|
|
8
15
|
/**
|
|
9
16
|
* @param context The context to bind to the event handler.
|
|
10
|
-
* @param
|
|
17
|
+
* @param eventHandler The event handler to call when the event is published.
|
|
11
18
|
*/
|
|
12
|
-
constructor(context: unknown,
|
|
19
|
+
constructor(context: unknown, eventHandler: EventHandler);
|
|
13
20
|
/**
|
|
14
21
|
* Call the event handler for the provided event.
|
|
15
22
|
*
|
|
@@ -59,16 +66,24 @@ declare class Subscription {
|
|
|
59
66
|
/** A class that allows objects to subscribe to events and be notified when the event is published. */
|
|
60
67
|
declare class Subscribr {
|
|
61
68
|
private readonly subscribers;
|
|
62
|
-
|
|
69
|
+
private errorHandler?;
|
|
70
|
+
/**
|
|
71
|
+
* Set a custom error handler for handling errors that occur in event listeners.
|
|
72
|
+
* If not set, errors will be logged to the console.
|
|
73
|
+
*
|
|
74
|
+
* @param errorHandler The error handler function to call when an error occurs in an event listener.
|
|
75
|
+
*/
|
|
76
|
+
setErrorHandler(errorHandler: ErrorHandler): void;
|
|
63
77
|
/**
|
|
64
78
|
* Subscribe to an event
|
|
65
79
|
*
|
|
66
80
|
* @param eventName The event name to subscribe to.
|
|
67
81
|
* @param eventHandler The event handler to call when the event is published.
|
|
68
82
|
* @param context The context to bind to the event handler.
|
|
83
|
+
* @param options Subscription options.
|
|
69
84
|
* @returns An object used to check if the subscription still exists and to unsubscribe from the event.
|
|
70
85
|
*/
|
|
71
|
-
subscribe(eventName: string, eventHandler:
|
|
86
|
+
subscribe(eventName: string, eventHandler: EventHandler, context?: unknown, options?: SubscriptionOptions): Subscription;
|
|
72
87
|
/**
|
|
73
88
|
* Unsubscribe from the event
|
|
74
89
|
*
|
|
@@ -92,6 +107,18 @@ declare class Subscribr {
|
|
|
92
107
|
* @returns true if the event name and handler are subscribed, false otherwise.
|
|
93
108
|
*/
|
|
94
109
|
isSubscribed({ eventName, contextEventHandler }: Subscription): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Validate the event name
|
|
112
|
+
*
|
|
113
|
+
* @param eventName The event name to validate.
|
|
114
|
+
* @throws {TypeError} If the event name is not a non-empty string.
|
|
115
|
+
* @throws {Error} If the event name has leading or trailing whitespace.
|
|
116
|
+
*/
|
|
117
|
+
private validateEventName;
|
|
118
|
+
/**
|
|
119
|
+
* Clears all subscriptions. The instance should not be used after calling this method.
|
|
120
|
+
*/
|
|
121
|
+
destroy(): void;
|
|
95
122
|
/**
|
|
96
123
|
* A String value that is used in the creation of the default string
|
|
97
124
|
* description of an object. Called by the built-in method {@link Object.prototype.toString}.
|
|
@@ -101,4 +128,5 @@ declare class Subscribr {
|
|
|
101
128
|
get [Symbol.toStringTag](): string;
|
|
102
129
|
}
|
|
103
130
|
|
|
104
|
-
export { ContextEventHandler,
|
|
131
|
+
export { ContextEventHandler, Subscribr, Subscription };
|
|
132
|
+
export type { ErrorHandler, EventHandler, SubscriptionOptions };
|
package/dist/subscribr.js
CHANGED
|
@@ -1,2 +1,257 @@
|
|
|
1
|
-
|
|
1
|
+
// node_modules/.pnpm/@d1g1tal+collections@2.1.1_typescript@5.9.3/node_modules/@d1g1tal/collections/src/set-multi-map.ts
|
|
2
|
+
var SetMultiMap = class extends Map {
|
|
3
|
+
/**
|
|
4
|
+
* Adds a new element with a specified key and value to the SetMultiMap.
|
|
5
|
+
* If an element with the same key already exists, the value will be added to the underlying {@link Set}.
|
|
6
|
+
* If the value already exists in the {@link Set}, it will not be added again.
|
|
7
|
+
*
|
|
8
|
+
* @param key The key to set.
|
|
9
|
+
* @param value The value to add to the SetMultiMap
|
|
10
|
+
* @returns The SetMultiMap with the updated key and value.
|
|
11
|
+
*/
|
|
12
|
+
set(key, value) {
|
|
13
|
+
super.set(key, value instanceof Set ? value : (super.get(key) ?? /* @__PURE__ */ new Set()).add(value));
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Finds a specific value for a specific key using an iterator function.
|
|
18
|
+
* @param key The key to find the value for.
|
|
19
|
+
* @param iterator The iterator function to use to find the value.
|
|
20
|
+
* @returns The value for the specified key
|
|
21
|
+
*/
|
|
22
|
+
find(key, iterator) {
|
|
23
|
+
const values = this.get(key);
|
|
24
|
+
if (values !== void 0) {
|
|
25
|
+
return Array.from(values).find(iterator);
|
|
26
|
+
}
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks if a specific key has a specific value.
|
|
31
|
+
*
|
|
32
|
+
* @param key The key to check.
|
|
33
|
+
* @param value The value to check.
|
|
34
|
+
* @returns True if the key has the value, false otherwise.
|
|
35
|
+
*/
|
|
36
|
+
hasValue(key, value) {
|
|
37
|
+
const values = super.get(key);
|
|
38
|
+
return values ? values.has(value) : false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Removes a specific value from a specific key.
|
|
42
|
+
* @param key The key to remove the value from.
|
|
43
|
+
* @param value The value to remove.
|
|
44
|
+
* @returns True if the value was removed, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
deleteValue(key, value) {
|
|
47
|
+
if (value === void 0) {
|
|
48
|
+
return this.delete(key);
|
|
49
|
+
}
|
|
50
|
+
const values = super.get(key);
|
|
51
|
+
if (values) {
|
|
52
|
+
const deleted = values.delete(value);
|
|
53
|
+
if (values.size === 0) {
|
|
54
|
+
super.delete(key);
|
|
55
|
+
}
|
|
56
|
+
return deleted;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The string tag of the SetMultiMap.
|
|
62
|
+
* @returns The string tag of the SetMultiMap.
|
|
63
|
+
*/
|
|
64
|
+
get [Symbol.toStringTag]() {
|
|
65
|
+
return "SetMultiMap";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/context-event-handler.ts
|
|
70
|
+
var ContextEventHandler = class {
|
|
71
|
+
context;
|
|
72
|
+
eventHandler;
|
|
73
|
+
/**
|
|
74
|
+
* @param context The context to bind to the event handler.
|
|
75
|
+
* @param eventHandler The event handler to call when the event is published.
|
|
76
|
+
*/
|
|
77
|
+
constructor(context, eventHandler) {
|
|
78
|
+
this.context = context;
|
|
79
|
+
this.eventHandler = eventHandler;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Call the event handler for the provided event.
|
|
83
|
+
*
|
|
84
|
+
* @param event The event to handle
|
|
85
|
+
* @param data The value to be passed to the event handler as a parameter.
|
|
86
|
+
*/
|
|
87
|
+
handle(event, data) {
|
|
88
|
+
this.eventHandler.call(this.context, event, data);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A String value that is used in the creation of the default string
|
|
92
|
+
* description of an object. Called by the built-in method {@link Object.prototype.toString}.
|
|
93
|
+
*
|
|
94
|
+
* @returns The default string description of this object.
|
|
95
|
+
*/
|
|
96
|
+
get [Symbol.toStringTag]() {
|
|
97
|
+
return "ContextEventHandler";
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/subscription.ts
|
|
102
|
+
var Subscription = class {
|
|
103
|
+
_eventName;
|
|
104
|
+
_contextEventHandler;
|
|
105
|
+
/**
|
|
106
|
+
* @param eventName The event name.
|
|
107
|
+
* @param contextEventHandler The context event handler.
|
|
108
|
+
*/
|
|
109
|
+
constructor(eventName, contextEventHandler) {
|
|
110
|
+
this._eventName = eventName;
|
|
111
|
+
this._contextEventHandler = contextEventHandler;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Gets the event name for the subscription.
|
|
115
|
+
*
|
|
116
|
+
* @returns The event name.
|
|
117
|
+
*/
|
|
118
|
+
get eventName() {
|
|
119
|
+
return this._eventName;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Gets the context event handler.
|
|
123
|
+
*
|
|
124
|
+
* @returns The context event handler
|
|
125
|
+
*/
|
|
126
|
+
get contextEventHandler() {
|
|
127
|
+
return this._contextEventHandler;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A String value that is used in the creation of the default string
|
|
131
|
+
* description of an object. Called by the built-in method {@link Object.prototype.toString}.
|
|
132
|
+
*
|
|
133
|
+
* @returns The default string description of this object.
|
|
134
|
+
*/
|
|
135
|
+
get [Symbol.toStringTag]() {
|
|
136
|
+
return "Subscription";
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/subscribr.ts
|
|
141
|
+
var Subscribr = class {
|
|
142
|
+
subscribers = new SetMultiMap();
|
|
143
|
+
errorHandler;
|
|
144
|
+
/**
|
|
145
|
+
* Set a custom error handler for handling errors that occur in event listeners.
|
|
146
|
+
* If not set, errors will be logged to the console.
|
|
147
|
+
*
|
|
148
|
+
* @param errorHandler The error handler function to call when an error occurs in an event listener.
|
|
149
|
+
*/
|
|
150
|
+
setErrorHandler(errorHandler) {
|
|
151
|
+
this.errorHandler = errorHandler;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Subscribe to an event
|
|
155
|
+
*
|
|
156
|
+
* @param eventName The event name to subscribe to.
|
|
157
|
+
* @param eventHandler The event handler to call when the event is published.
|
|
158
|
+
* @param context The context to bind to the event handler.
|
|
159
|
+
* @param options Subscription options.
|
|
160
|
+
* @returns An object used to check if the subscription still exists and to unsubscribe from the event.
|
|
161
|
+
*/
|
|
162
|
+
subscribe(eventName, eventHandler, context = eventHandler, options) {
|
|
163
|
+
this.validateEventName(eventName);
|
|
164
|
+
if (options?.once) {
|
|
165
|
+
const originalHandler = eventHandler;
|
|
166
|
+
eventHandler = (event, data) => {
|
|
167
|
+
originalHandler.call(context, event, data);
|
|
168
|
+
this.unsubscribe(subscription);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const contextEventHandler = new ContextEventHandler(context, eventHandler);
|
|
172
|
+
this.subscribers.set(eventName, contextEventHandler);
|
|
173
|
+
const subscription = new Subscription(eventName, contextEventHandler);
|
|
174
|
+
return subscription;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Unsubscribe from the event
|
|
178
|
+
*
|
|
179
|
+
* @param subscription The subscription to unsubscribe.
|
|
180
|
+
* @returns true if eventListener has been removed successfully. false if the value is not found or if the value is not an object.
|
|
181
|
+
*/
|
|
182
|
+
unsubscribe({ eventName, contextEventHandler }) {
|
|
183
|
+
const contextEventHandlers = this.subscribers.get(eventName) ?? /* @__PURE__ */ new Set();
|
|
184
|
+
const removed = contextEventHandlers.delete(contextEventHandler);
|
|
185
|
+
if (removed && contextEventHandlers.size === 0) {
|
|
186
|
+
this.subscribers.delete(eventName);
|
|
187
|
+
}
|
|
188
|
+
return removed;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Publish an event
|
|
192
|
+
*
|
|
193
|
+
* @template T
|
|
194
|
+
* @param eventName The name of the event.
|
|
195
|
+
* @param event The event to be handled.
|
|
196
|
+
* @param data The value to be passed to the event handler as a parameter.
|
|
197
|
+
*/
|
|
198
|
+
publish(eventName, event = new CustomEvent(eventName), data) {
|
|
199
|
+
this.validateEventName(eventName);
|
|
200
|
+
this.subscribers.get(eventName)?.forEach((contextEventHandler) => {
|
|
201
|
+
try {
|
|
202
|
+
contextEventHandler.handle(event, data);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (this.errorHandler) {
|
|
205
|
+
this.errorHandler(error, eventName, event, data);
|
|
206
|
+
} else {
|
|
207
|
+
console.error(`Error in event handler for '${eventName}':`, error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if the event and handler are subscribed.
|
|
214
|
+
*
|
|
215
|
+
* @param subscription The subscription object.
|
|
216
|
+
* @returns true if the event name and handler are subscribed, false otherwise.
|
|
217
|
+
*/
|
|
218
|
+
isSubscribed({ eventName, contextEventHandler }) {
|
|
219
|
+
return this.subscribers.get(eventName)?.has(contextEventHandler) ?? false;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Validate the event name
|
|
223
|
+
*
|
|
224
|
+
* @param eventName The event name to validate.
|
|
225
|
+
* @throws {TypeError} If the event name is not a non-empty string.
|
|
226
|
+
* @throws {Error} If the event name has leading or trailing whitespace.
|
|
227
|
+
*/
|
|
228
|
+
validateEventName(eventName) {
|
|
229
|
+
if (!eventName || typeof eventName !== "string") {
|
|
230
|
+
throw new TypeError("Event name must be a non-empty string");
|
|
231
|
+
}
|
|
232
|
+
if (eventName.trim() !== eventName) {
|
|
233
|
+
throw new Error("Event name cannot have leading or trailing whitespace");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Clears all subscriptions. The instance should not be used after calling this method.
|
|
238
|
+
*/
|
|
239
|
+
destroy() {
|
|
240
|
+
this.subscribers.clear();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A String value that is used in the creation of the default string
|
|
244
|
+
* description of an object. Called by the built-in method {@link Object.prototype.toString}.
|
|
245
|
+
*
|
|
246
|
+
* @returns The default string description of this object.
|
|
247
|
+
*/
|
|
248
|
+
get [Symbol.toStringTag]() {
|
|
249
|
+
return "Subscribr";
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
export {
|
|
253
|
+
ContextEventHandler,
|
|
254
|
+
Subscribr,
|
|
255
|
+
Subscription
|
|
256
|
+
};
|
|
2
257
|
//# sourceMappingURL=subscribr.js.map
|
package/dist/subscribr.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../node_modules/.pnpm/@d1g1tal+collections@2.
|
|
4
|
-
"sourcesContent": ["/** A {@link Map} that can contain multiple, unique, values for the same key. */\nexport class SetMultiMap<K, V> extends Map<K, Set<V>>{\n\t/**\n\t * Adds a new element with a specified key and value to the SetMultiMap.\n\t * If an element with the same key already exists, the value will be added to the underlying {@link Set}.\n\t * If the value already exists in the {@link Set}, it will not be added again.\n\t *\n\t * @param {K} key The key to set.\n\t * @param {V} value The value to add to the SetMultiMap\n\t * @returns {SetMultiMap<K, V>} The SetMultiMap with the updated key and value.\n\t */\n\t// @ts-expect-error I am overriding the set method from the Map class\n\toverride set(key: K, value: V): SetMultiMap<K, V> {\n\t\tsuper.set(key, (super.get(key) ?? new Set()).add(value));\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks if a specific key has a specific value.\n\t *\n\t * @param {K} key The key to check.\n\t * @param {V} value The value to check.\n\t * @returns {boolean} True if the key has the value, false otherwise.\n\t */\n\thasValue(key: K, value: V): boolean {\n\t\tconst values = super.get(key);\n\n\t\treturn values ? values.has(value) : false;\n\t}\n\n\tfind(key: K, iterator: (value: V) => boolean): V | undefined {\n\t\tconst values = this.get(key);\n\n\t\tif (values !== undefined) {\n\t\t\treturn Array.from(values).find(iterator);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes a specific value from a specific key.\n\t *\n\t * @param {K} key The key to remove the value from.\n\t * @param {V | undefined} value The value to remove.\n\t * @returns {boolean} True if the value was removed, false otherwise.\n\t */\n\tdeleteValue(key: K, value: V | undefined): boolean {\n\t\tif (value === undefined) { return this.delete(key) }\n\n\t\tconst values = super.get(key);\n\t\tif (values) {\n\t\t\tconst deleted = values.delete(value);\n\n\t\t\tif (values.size === 0) {\n\t\t\t\tsuper.delete(key);\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\toverride get [Symbol.toStringTag](): string {\n\t\treturn 'SetMultiMap';\n\t}\n}", "import type { ContextEventListener } from './@types';\n\n/** A wrapper for an event handler that binds a context to the event handler. */\nexport class ContextEventHandler {\n\tprivate readonly context: unknown;\n\tprivate readonly eventListener: ContextEventListener;\n\n\t/**\n\t * @param context The context to bind to the event handler.\n\t * @param eventListener The event handler to call when the event is published.\n\t */\n\tconstructor(context: unknown, eventListener: ContextEventListener) {\n\t\tthis.context = context;\n\t\tthis.eventListener = eventListener;\n\t}\n\n\t/**\n\t * Call the event handler for the provided event.\n\t *\n\t * @param event The event to handle\n\t * @param data The value to be passed to the event handler as a parameter.\n\t */\n\thandle(event: Event, data?: unknown): void {\n\t\tthis.eventListener.call(this.context, event, data);\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'ContextEventHandler';\n\t}\n}", "import type { ContextEventHandler } from './context-event-handler';\n\n/** Represents a subscription to an event. */\nexport class Subscription {\n\tprivate readonly _eventName: string;\n\tprivate readonly _contextEventHandler: ContextEventHandler;\n\n\t/**\n\t * @param eventName The event name.\n\t * @param contextEventHandler The context event handler.\n\t */\n\tconstructor(eventName: string, contextEventHandler: ContextEventHandler) {\n\t\tthis._eventName = eventName;\n\t\tthis._contextEventHandler = contextEventHandler;\n\t}\n\n\t/**\n\t * Gets the event name for the subscription.\n\t *\n\t * @returns The event name.\n\t */\n\tget eventName(): string {\n\t\treturn this._eventName;\n\t}\n\n\t/**\n\t * Gets the context event handler.\n\t *\n\t * @returns The context event handler\n\t */\n\tget contextEventHandler(): ContextEventHandler {\n\t\treturn this._contextEventHandler;\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'Subscription';\n\t}\n}", "import { SetMultiMap } from '@d1g1tal/collections/src';\nimport { ContextEventHandler } from './context-event-handler';\nimport { Subscription } from './subscription';\nimport type { ContextEventListener } from './@types';\n\n/** A class that allows objects to subscribe to events and be notified when the event is published. */\nexport class Subscribr {\n\tprivate readonly subscribers: SetMultiMap<string, ContextEventHandler>;\n\n\tconstructor() {\n\t\tthis.subscribers = new SetMultiMap();\n\t}\n\n\t/**\n\t * Subscribe to an event\n\t *\n\t * @param eventName The event name to subscribe to.\n\t * @param eventHandler The event handler to call when the event is published.\n\t * @param context The context to bind to the event handler.\n\t * @returns An object used to check if the subscription still exists and to unsubscribe from the event.\n\t */\n\tsubscribe(eventName: string, eventHandler: ContextEventListener, context: unknown = eventHandler): Subscription {\n\t\tconst contextEventHandler = new ContextEventHandler(context, eventHandler);\n\t\tthis.subscribers.set(eventName, contextEventHandler);\n\n\t\treturn new Subscription(eventName, contextEventHandler);\n\t}\n\n\t/**\n\t * Unsubscribe from the event\n\t *\n\t * @param subscription The subscription to unsubscribe.\n\t * @returns true if eventListener has been removed successfully. false if the value is not found or if the value is not an object.\n\t */\n\tunsubscribe({ eventName, contextEventHandler }: Subscription): boolean {\n\t\tconst contextEventHandlers = this.subscribers.get(eventName) ?? new Set();\n\t\tconst removed = contextEventHandlers.delete(contextEventHandler);\n\n\t\tif (removed && contextEventHandlers.size === 0) {\tthis.subscribers.delete(eventName) }\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Publish an event\n\t *\n\t * @template T\n\t * @param eventName The name of the event.\n\t * @param event The event to be handled.\n\t * @param data The value to be passed to the event handler as a parameter.\n\t */\n\tpublish<T>(eventName: string, event: Event = new CustomEvent(eventName), data?: T): void {\n\t\tthis.subscribers.get(eventName)?.forEach((contextEventHandler: ContextEventHandler) => contextEventHandler.handle(event, data));\n\t}\n\n\t/**\n\t * Check if the event and handler are subscribed.\n\t *\n\t * @param subscription The subscription object.\n\t * @returns true if the event name and handler are subscribed, false otherwise.\n\t */\n\tisSubscribed({ eventName, contextEventHandler }: Subscription): boolean {\n\t\treturn this.subscribers.get(eventName)?.has(contextEventHandler) ?? false;\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'Subscribr';\n\t}\n}"],
|
|
5
|
-
"mappings": "AACO,
|
|
6
|
-
"names": [
|
|
3
|
+
"sources": ["../node_modules/.pnpm/@d1g1tal+collections@2.1.1_typescript@5.9.3/node_modules/@d1g1tal/collections/src/set-multi-map.ts", "../src/context-event-handler.ts", "../src/subscription.ts", "../src/subscribr.ts"],
|
|
4
|
+
"sourcesContent": ["/** A {@link Map} that can contain multiple, unique, values for the same key. */\nexport class SetMultiMap<K, V> extends Map<K, Set<V>>{\n\t/**\n\t * Adds a new element with a specified key and value to the SetMultiMap.\n\t * If an element with the same key already exists, the value will be added to the underlying {@link Set}.\n\t * If the value already exists in the {@link Set}, it will not be added again.\n\t *\n\t * @param key - The key to set.\n\t * @param value - The value to add to the SetMultiMap.\n\t * @returns The SetMultiMap with the updated key and value.\n\t */\n\toverride set(key: K, value: V): this;\n\t/**\n\t * Adds a new Set with a specified key and value to the SetMultiMap.\n\t * If an element with the same key already exists, the value will be added to the underlying {@link Set}.\n\t * If the value already exists in the {@link Set}, it will not be added again.\n\t *\n\t * @param key - The key to set.\n\t * @param value - The set of values to add to the SetMultiMap.\n\t * @returns The SetMultiMap with the updated key and value.\n\t */\n\toverride set(key: K, value: Set<V>): this;\n\t/**\n\t * Adds a new element with a specified key and value to the SetMultiMap.\n\t * If an element with the same key already exists, the value will be added to the underlying {@link Set}.\n\t * If the value already exists in the {@link Set}, it will not be added again.\n\t *\n\t * @param key The key to set.\n\t * @param value The value to add to the SetMultiMap\n\t * @returns The SetMultiMap with the updated key and value.\n\t */\n\toverride set(key: K, value: V | Set<V>): SetMultiMap<K, V> {\n\t\tsuper.set(key, value instanceof Set ? value : (super.get(key) ?? new Set<V>()).add(value));\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Finds a specific value for a specific key using an iterator function.\n\t * @param key The key to find the value for.\n\t * @param iterator The iterator function to use to find the value.\n\t * @returns The value for the specified key\n\t */\n\tfind(key: K, iterator: (value: V) => boolean): V | undefined {\n\t\tconst values = this.get(key);\n\n\t\tif (values !== undefined) {\n\t\t\treturn Array.from(values).find(iterator);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Checks if a specific key has a specific value.\n\t *\n\t * @param key The key to check.\n\t * @param value The value to check.\n\t * @returns True if the key has the value, false otherwise.\n\t */\n\thasValue(key: K, value: V): boolean {\n\t\tconst values = super.get(key);\n\n\t\treturn values ? values.has(value) : false;\n\t}\n\n\t/**\n\t * Removes a specific value from a specific key.\n\t * @param key The key to remove the value from.\n\t * @param value The value to remove.\n\t * @returns True if the value was removed, false otherwise.\n\t */\n\tdeleteValue(key: K, value: V | undefined): boolean {\n\t\tif (value === undefined) { return this.delete(key) }\n\n\t\tconst values = super.get(key);\n\t\tif (values) {\n\t\t\tconst deleted = values.delete(value);\n\n\t\t\tif (values.size === 0) {\n\t\t\t\tsuper.delete(key);\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * The string tag of the SetMultiMap.\n\t * @returns The string tag of the SetMultiMap.\n\t */\n\toverride get [Symbol.toStringTag](): string {\n\t\treturn 'SetMultiMap';\n\t}\n}", "import type { EventHandler } from './@types';\n\n/** A wrapper for an event handler that binds a context to the event handler. */\nexport class ContextEventHandler {\n\tprivate readonly context: unknown;\n\tprivate readonly eventHandler: EventHandler;\n\n\t/**\n\t * @param context The context to bind to the event handler.\n\t * @param eventHandler The event handler to call when the event is published.\n\t */\n\tconstructor(context: unknown, eventHandler: EventHandler) {\n\t\tthis.context = context;\n\t\tthis.eventHandler = eventHandler;\n\t}\n\n\t/**\n\t * Call the event handler for the provided event.\n\t *\n\t * @param event The event to handle\n\t * @param data The value to be passed to the event handler as a parameter.\n\t */\n\thandle(event: Event, data?: unknown): void {\n\t\tthis.eventHandler.call(this.context, event, data);\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'ContextEventHandler';\n\t}\n}", "import type { ContextEventHandler } from './context-event-handler';\n\n/** Represents a subscription to an event. */\nexport class Subscription {\n\tprivate readonly _eventName: string;\n\tprivate readonly _contextEventHandler: ContextEventHandler;\n\n\t/**\n\t * @param eventName The event name.\n\t * @param contextEventHandler The context event handler.\n\t */\n\tconstructor(eventName: string, contextEventHandler: ContextEventHandler) {\n\t\tthis._eventName = eventName;\n\t\tthis._contextEventHandler = contextEventHandler;\n\t}\n\n\t/**\n\t * Gets the event name for the subscription.\n\t *\n\t * @returns The event name.\n\t */\n\tget eventName(): string {\n\t\treturn this._eventName;\n\t}\n\n\t/**\n\t * Gets the context event handler.\n\t *\n\t * @returns The context event handler\n\t */\n\tget contextEventHandler(): ContextEventHandler {\n\t\treturn this._contextEventHandler;\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'Subscription';\n\t}\n}", "import { SetMultiMap } from '@d1g1tal/collections/src';\nimport { ContextEventHandler } from './context-event-handler';\nimport { Subscription } from './subscription';\nimport type { EventHandler, ErrorHandler, SubscriptionOptions } from './@types';\n\n/** A class that allows objects to subscribe to events and be notified when the event is published. */\nexport class Subscribr {\n\tprivate readonly subscribers: SetMultiMap<string, ContextEventHandler> = new SetMultiMap();\n\tprivate errorHandler?: ErrorHandler;\n\n\t/**\n\t * Set a custom error handler for handling errors that occur in event listeners.\n\t * If not set, errors will be logged to the console.\n\t *\n\t * @param errorHandler The error handler function to call when an error occurs in an event listener.\n\t */\n\tsetErrorHandler(errorHandler: ErrorHandler): void {\n\t\tthis.errorHandler = errorHandler;\n\t}\n\n\t/**\n\t * Subscribe to an event\n\t *\n\t * @param eventName The event name to subscribe to.\n\t * @param eventHandler The event handler to call when the event is published.\n\t * @param context The context to bind to the event handler.\n\t * @param options Subscription options.\n\t * @returns An object used to check if the subscription still exists and to unsubscribe from the event.\n\t */\n\tsubscribe(eventName: string, eventHandler: EventHandler, context: unknown = eventHandler, options?: SubscriptionOptions): Subscription {\n\t\tthis.validateEventName(eventName);\n\n\t\t// If once option is set, wrap the handler to auto-unsubscribe\n\t\tif (options?.once) {\n\t\t\tconst originalHandler = eventHandler;\n\t\t\teventHandler = (event: Event, data?: unknown) => {\n\t\t\t\toriginalHandler.call(context, event, data);\n\t\t\t\tthis.unsubscribe(subscription);\n\t\t\t};\n\t\t}\n\n\t\tconst contextEventHandler = new ContextEventHandler(context, eventHandler);\n\t\tthis.subscribers.set(eventName, contextEventHandler);\n\n\t\tconst subscription = new Subscription(eventName, contextEventHandler);\n\n\t\treturn subscription;\n\t}\n\n\t/**\n\t * Unsubscribe from the event\n\t *\n\t * @param subscription The subscription to unsubscribe.\n\t * @returns true if eventListener has been removed successfully. false if the value is not found or if the value is not an object.\n\t */\n\tunsubscribe({ eventName, contextEventHandler }: Subscription): boolean {\n\t\tconst contextEventHandlers = this.subscribers.get(eventName) ?? new Set();\n\t\tconst removed = contextEventHandlers.delete(contextEventHandler);\n\n\t\tif (removed && contextEventHandlers.size === 0) {\tthis.subscribers.delete(eventName) }\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Publish an event\n\t *\n\t * @template T\n\t * @param eventName The name of the event.\n\t * @param event The event to be handled.\n\t * @param data The value to be passed to the event handler as a parameter.\n\t */\n\tpublish<T>(eventName: string, event: Event = new CustomEvent(eventName), data?: T): void {\n\t\tthis.validateEventName(eventName);\n\t\tthis.subscribers.get(eventName)?.forEach((contextEventHandler: ContextEventHandler) => {\n\t\t\ttry {\n\t\t\t\tcontextEventHandler.handle(event, data);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.errorHandler) {\n\t\t\t\t\tthis.errorHandler(error as Error, eventName, event, data);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`Error in event handler for '${eventName}':`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Check if the event and handler are subscribed.\n\t *\n\t * @param subscription The subscription object.\n\t * @returns true if the event name and handler are subscribed, false otherwise.\n\t */\n\tisSubscribed({ eventName, contextEventHandler }: Subscription): boolean {\n\t\treturn this.subscribers.get(eventName)?.has(contextEventHandler) ?? false;\n\t}\n\n\t/**\n\t * Validate the event name\n\t *\n\t * @param eventName The event name to validate.\n\t * @throws {TypeError} If the event name is not a non-empty string.\n\t * @throws {Error} If the event name has leading or trailing whitespace.\n\t */\n\tprivate validateEventName(eventName: string): void {\n\t\tif (!eventName || typeof eventName !== 'string') {\n\t\t\tthrow new TypeError('Event name must be a non-empty string');\n\t\t}\n\n\t\tif (eventName.trim() !== eventName) {\n\t\t\tthrow new Error('Event name cannot have leading or trailing whitespace');\n\t\t}\n\t}\n\n\t/**\n\t * Clears all subscriptions. The instance should not be used after calling this method.\n\t */\n\tdestroy(): void {\n\t\tthis.subscribers.clear();\n\t}\n\n\t/**\n\t * A String value that is used in the creation of the default string\n\t * description of an object. Called by the built-in method {@link Object.prototype.toString}.\n\t *\n\t * @returns The default string description of this object.\n\t */\n\tget [Symbol.toStringTag](): string {\n\t\treturn 'Subscribr';\n\t}\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,75 +1,72 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"prepublish": "pnpm lint && pnpm test && pnpm -s build"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
2
|
+
"name": "@d1g1tal/subscribr",
|
|
3
|
+
"version": "4.1.8",
|
|
4
|
+
"packageManager": "pnpm@10.30.1",
|
|
5
|
+
"description": "JavaScript Publish/Subscribe Library",
|
|
6
|
+
"author": "Jason DiMeo",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"homepage": "https://github.com/D1g1talEntr0py/subscribr#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/D1g1talEntr0py/subscribr/issues"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"JavaScript",
|
|
14
|
+
"ObserverPattern",
|
|
15
|
+
"Publish/Subscribe"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/subscribr.d.ts",
|
|
24
|
+
"import": "./dist/subscribr.js"
|
|
25
|
+
},
|
|
26
|
+
"./src": {
|
|
27
|
+
"types": "./dist/subscribr.d.ts",
|
|
28
|
+
"import": "./src/index.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"/src",
|
|
33
|
+
"/dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsbuild",
|
|
37
|
+
"build:minify": "tsbuild --minify --force",
|
|
38
|
+
"build:watch": "tsbuild --watch",
|
|
39
|
+
"type-check": "tsbuild --noEmit",
|
|
40
|
+
"lint": "eslint ./src",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:coverage": "vitest run --coverage",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"prepublish": "pnpm lint && pnpm test && pnpm -s build --force"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/D1g1talEntr0py/subscribr.git"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@d1g1tal/collections": "^2.1.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@d1g1tal/tsbuild": "1.0.0",
|
|
55
|
+
"@eslint/compat": "^2.0.2",
|
|
56
|
+
"@eslint/js": "^10.0.1",
|
|
57
|
+
"@types/node": "^25.3.0",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.56.0",
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
61
|
+
"eslint": "^10.0.1",
|
|
62
|
+
"eslint-plugin-compat": "^6.2.0",
|
|
63
|
+
"eslint-plugin-jsdoc": "^62.7.0",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.56.0",
|
|
66
|
+
"vitest": "^4.0.18"
|
|
67
|
+
},
|
|
68
|
+
"browserslist": [
|
|
69
|
+
"defaults and fully supports es6-module",
|
|
70
|
+
"node >= 20.15.1"
|
|
71
|
+
]
|
|
72
|
+
}
|
package/src/@types/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/** Context event listener function. */
|
|
2
|
-
type
|
|
2
|
+
type EventHandler = (event: Event, data?: unknown) => void;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/** Error handler function for handling errors in event listeners. */
|
|
5
|
+
type ErrorHandler = (error: Error, eventName: string, event: Event, data?: unknown) => void;
|
|
6
|
+
|
|
7
|
+
/** Subscription options. */
|
|
8
|
+
interface SubscriptionOptions {
|
|
9
|
+
/** If true, the subscription will automatically unsubscribe after the first event. */
|
|
10
|
+
once?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type { EventHandler, ErrorHandler, SubscriptionOptions };
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EventHandler } from './@types';
|
|
2
2
|
|
|
3
3
|
/** A wrapper for an event handler that binds a context to the event handler. */
|
|
4
4
|
export class ContextEventHandler {
|
|
5
5
|
private readonly context: unknown;
|
|
6
|
-
private readonly
|
|
6
|
+
private readonly eventHandler: EventHandler;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param context The context to bind to the event handler.
|
|
10
|
-
* @param
|
|
10
|
+
* @param eventHandler The event handler to call when the event is published.
|
|
11
11
|
*/
|
|
12
|
-
constructor(context: unknown,
|
|
12
|
+
constructor(context: unknown, eventHandler: EventHandler) {
|
|
13
13
|
this.context = context;
|
|
14
|
-
this.
|
|
14
|
+
this.eventHandler = eventHandler;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -21,7 +21,7 @@ export class ContextEventHandler {
|
|
|
21
21
|
* @param data The value to be passed to the event handler as a parameter.
|
|
22
22
|
*/
|
|
23
23
|
handle(event: Event, data?: unknown): void {
|
|
24
|
-
this.
|
|
24
|
+
this.eventHandler.call(this.context, event, data);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
package/src/index.ts
CHANGED
package/src/subscribr.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { SetMultiMap } from '@d1g1tal/collections/src';
|
|
2
2
|
import { ContextEventHandler } from './context-event-handler';
|
|
3
3
|
import { Subscription } from './subscription';
|
|
4
|
-
import type {
|
|
4
|
+
import type { EventHandler, ErrorHandler, SubscriptionOptions } from './@types';
|
|
5
5
|
|
|
6
6
|
/** A class that allows objects to subscribe to events and be notified when the event is published. */
|
|
7
7
|
export class Subscribr {
|
|
8
|
-
private readonly subscribers: SetMultiMap<string, ContextEventHandler
|
|
8
|
+
private readonly subscribers: SetMultiMap<string, ContextEventHandler> = new SetMultiMap();
|
|
9
|
+
private errorHandler?: ErrorHandler;
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Set a custom error handler for handling errors that occur in event listeners.
|
|
13
|
+
* If not set, errors will be logged to the console.
|
|
14
|
+
*
|
|
15
|
+
* @param errorHandler The error handler function to call when an error occurs in an event listener.
|
|
16
|
+
*/
|
|
17
|
+
setErrorHandler(errorHandler: ErrorHandler): void {
|
|
18
|
+
this.errorHandler = errorHandler;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
/**
|
|
@@ -17,13 +24,27 @@ export class Subscribr {
|
|
|
17
24
|
* @param eventName The event name to subscribe to.
|
|
18
25
|
* @param eventHandler The event handler to call when the event is published.
|
|
19
26
|
* @param context The context to bind to the event handler.
|
|
27
|
+
* @param options Subscription options.
|
|
20
28
|
* @returns An object used to check if the subscription still exists and to unsubscribe from the event.
|
|
21
29
|
*/
|
|
22
|
-
subscribe(eventName: string, eventHandler:
|
|
30
|
+
subscribe(eventName: string, eventHandler: EventHandler, context: unknown = eventHandler, options?: SubscriptionOptions): Subscription {
|
|
31
|
+
this.validateEventName(eventName);
|
|
32
|
+
|
|
33
|
+
// If once option is set, wrap the handler to auto-unsubscribe
|
|
34
|
+
if (options?.once) {
|
|
35
|
+
const originalHandler = eventHandler;
|
|
36
|
+
eventHandler = (event: Event, data?: unknown) => {
|
|
37
|
+
originalHandler.call(context, event, data);
|
|
38
|
+
this.unsubscribe(subscription);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
const contextEventHandler = new ContextEventHandler(context, eventHandler);
|
|
24
43
|
this.subscribers.set(eventName, contextEventHandler);
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
const subscription = new Subscription(eventName, contextEventHandler);
|
|
46
|
+
|
|
47
|
+
return subscription;
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
/**
|
|
@@ -50,7 +71,18 @@ export class Subscribr {
|
|
|
50
71
|
* @param data The value to be passed to the event handler as a parameter.
|
|
51
72
|
*/
|
|
52
73
|
publish<T>(eventName: string, event: Event = new CustomEvent(eventName), data?: T): void {
|
|
53
|
-
this.
|
|
74
|
+
this.validateEventName(eventName);
|
|
75
|
+
this.subscribers.get(eventName)?.forEach((contextEventHandler: ContextEventHandler) => {
|
|
76
|
+
try {
|
|
77
|
+
contextEventHandler.handle(event, data);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (this.errorHandler) {
|
|
80
|
+
this.errorHandler(error as Error, eventName, event, data);
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`Error in event handler for '${eventName}':`, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
54
86
|
}
|
|
55
87
|
|
|
56
88
|
/**
|
|
@@ -63,6 +95,30 @@ export class Subscribr {
|
|
|
63
95
|
return this.subscribers.get(eventName)?.has(contextEventHandler) ?? false;
|
|
64
96
|
}
|
|
65
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Validate the event name
|
|
100
|
+
*
|
|
101
|
+
* @param eventName The event name to validate.
|
|
102
|
+
* @throws {TypeError} If the event name is not a non-empty string.
|
|
103
|
+
* @throws {Error} If the event name has leading or trailing whitespace.
|
|
104
|
+
*/
|
|
105
|
+
private validateEventName(eventName: string): void {
|
|
106
|
+
if (!eventName || typeof eventName !== 'string') {
|
|
107
|
+
throw new TypeError('Event name must be a non-empty string');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (eventName.trim() !== eventName) {
|
|
111
|
+
throw new Error('Event name cannot have leading or trailing whitespace');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Clears all subscriptions. The instance should not be used after calling this method.
|
|
117
|
+
*/
|
|
118
|
+
destroy(): void {
|
|
119
|
+
this.subscribers.clear();
|
|
120
|
+
}
|
|
121
|
+
|
|
66
122
|
/**
|
|
67
123
|
* A String value that is used in the creation of the default string
|
|
68
124
|
* description of an object. Called by the built-in method {@link Object.prototype.toString}.
|