@aionbuilders/pulse 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +538 -0
- package/dist/core/event.d.ts +88 -0
- package/dist/core/event.d.ts.map +1 -0
- package/dist/core/listener.d.ts +57 -0
- package/dist/core/listener.d.ts.map +1 -0
- package/dist/core/middleware.d.ts +41 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/pulse.d.ts +89 -0
- package/dist/core/pulse.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Killian Di Vincenzo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# @aionbuilders/pulse
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@aionbuilders/pulse)
|
|
4
|
+
[](https://bundlephobia.com/package/@aionbuilders/pulse)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
> A powerful, lightweight event system with pattern matching, middleware, and timeout support for JavaScript applications.
|
|
8
|
+
|
|
9
|
+
Pulse is a sophisticated event system that extends beyond basic pub/sub patterns. It provides hierarchical topic structures with support for wildcards, middleware chains, response handling, and automatic timeout management.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- ๐ **Hierarchical topic matching** with single (`*`), zero-or-more (`**`), and one-or-more (`++`) wildcards
|
|
14
|
+
- โก **Middleware support** for transforming and filtering events
|
|
15
|
+
- โฑ๏ธ **Built-in timeout handling** for asynchronous operations (default 5s)
|
|
16
|
+
- ๐ **Promise-based API** for modern JavaScript applications
|
|
17
|
+
- ๐ง **Smart event routing** based on pattern matching
|
|
18
|
+
- ๐พ **Context management** for storing data within events
|
|
19
|
+
- ๐ช **Typed with JSDoc** for excellent IDE integration
|
|
20
|
+
- ๐ชถ **Lightweight** with zero dependencies
|
|
21
|
+
- ๐งช **Thoroughly tested** with extensive unit tests
|
|
22
|
+
- ๐ก๏ธ **Unified error handling** across middlewares and listeners
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Using npm
|
|
28
|
+
npm install @aionbuilders/pulse
|
|
29
|
+
|
|
30
|
+
# Using yarn
|
|
31
|
+
yarn add @aionbuilders/pulse
|
|
32
|
+
|
|
33
|
+
# Using pnpm
|
|
34
|
+
pnpm add @aionbuilders/pulse
|
|
35
|
+
|
|
36
|
+
# Using bun
|
|
37
|
+
bun add @aionbuilders/pulse
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
import { Pulse } from '@aionbuilders/pulse';
|
|
44
|
+
|
|
45
|
+
// Create a new Pulse instance
|
|
46
|
+
const pulse = new Pulse();
|
|
47
|
+
|
|
48
|
+
// Subscribe to events
|
|
49
|
+
pulse.on('user:created', ({ event }) => {
|
|
50
|
+
console.log(`New user created: ${event.data.username}`);
|
|
51
|
+
event.respond({ success: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Subscribe with wildcards
|
|
55
|
+
pulse.on('user:*:updated', ({ event }) => {
|
|
56
|
+
console.log(`User property updated: ${event.topic}`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Add middleware
|
|
60
|
+
pulse.use('user:**', async ({ event }, next) => {
|
|
61
|
+
console.log(`[${new Date().toISOString()}] User event: ${event.topic}`);
|
|
62
|
+
// Call next to continue the middleware chain
|
|
63
|
+
await next();
|
|
64
|
+
console.log('Event processing completed');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Emit events
|
|
68
|
+
const event = await pulse.emit('user:created', {
|
|
69
|
+
username: 'john_doe',
|
|
70
|
+
email: 'john@example.com'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(event.responses[0]); // { success: true }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Core Concepts
|
|
77
|
+
|
|
78
|
+
### Topics and Patterns
|
|
79
|
+
|
|
80
|
+
Topics in Pulse are hierarchical, colon-separated strings:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
namespace:entity:action
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For example:
|
|
87
|
+
- `user:login`
|
|
88
|
+
- `chat:message:sent`
|
|
89
|
+
- `system:database:connected`
|
|
90
|
+
|
|
91
|
+
Listeners can use patterns to match multiple topics:
|
|
92
|
+
|
|
93
|
+
- `*`: Matches exactly one section
|
|
94
|
+
- `user:*` matches `user:login` and `user:logout` but not `user:profile:view`
|
|
95
|
+
|
|
96
|
+
- `**`: Matches zero or more sections
|
|
97
|
+
- `user:**` matches `user` (0 sections), `user:login` (1 section), and `user:profile:view` (2 sections)
|
|
98
|
+
- `**:login` matches `login` (0 sections before), `user:login`, `app:user:login`
|
|
99
|
+
- `user:**:deleted` matches `user:deleted` (0 sections between), `user:account:deleted`, etc.
|
|
100
|
+
|
|
101
|
+
- `++`: Matches one or more sections (new in v2.1.3)
|
|
102
|
+
- `user:++` matches `user:login` and `user:profile:view` but NOT `user`
|
|
103
|
+
- `++:login` matches `user:login` and `app:user:login` but NOT `login`
|
|
104
|
+
- `user:++:deleted` matches `user:account:deleted` but NOT `user:deleted`
|
|
105
|
+
|
|
106
|
+
### Events
|
|
107
|
+
|
|
108
|
+
When you emit a topic, Pulse creates an Event object containing:
|
|
109
|
+
|
|
110
|
+
- `topic`: The emitted topic
|
|
111
|
+
- `data`: The payload data
|
|
112
|
+
- `responses`: Array of response data from listeners
|
|
113
|
+
- `errors`: Array of errors that occurred during processing
|
|
114
|
+
- `timestamp`: When the event was created
|
|
115
|
+
- `id`: Unique event identifier
|
|
116
|
+
- `options`: Event options (silent, source, timeout)
|
|
117
|
+
|
|
118
|
+
### Middleware
|
|
119
|
+
|
|
120
|
+
Middleware functions allow for pre/post-processing of events:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
pulse.use('pattern', async ({ event, pulse, listener }, next) => {
|
|
124
|
+
// Pre-processing
|
|
125
|
+
console.log(`Processing ${event.topic}`);
|
|
126
|
+
|
|
127
|
+
// Continue chain (required to reach listeners)
|
|
128
|
+
await next();
|
|
129
|
+
|
|
130
|
+
// Post-processing (happens after listener execution)
|
|
131
|
+
console.log(`Responses: ${event.responses}`);
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Detailed Usage
|
|
136
|
+
|
|
137
|
+
### Basic Event Subscription
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// Basic subscription
|
|
141
|
+
pulse.on('topic', ({ event, pulse, listener }) => {
|
|
142
|
+
// Handle event
|
|
143
|
+
// event: the event object
|
|
144
|
+
// pulse: the pulse instance
|
|
145
|
+
// listener: the listener object
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Subscribe once (auto-remove after first call)
|
|
149
|
+
pulse.once('topic', ({ event }) => {
|
|
150
|
+
// This will only be called once
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// With options
|
|
154
|
+
pulse.on('topic', ({ event }) => {
|
|
155
|
+
// Handle event
|
|
156
|
+
}, {
|
|
157
|
+
once: true, // Auto-remove after being called once
|
|
158
|
+
autodestroy: {
|
|
159
|
+
calls: 5, // Remove after 5 calls
|
|
160
|
+
timeout: 60000 // Remove after 60 seconds
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Return a value to automatically add it to responses
|
|
165
|
+
pulse.on('greeting', ({ event }) => {
|
|
166
|
+
return `Hello, ${event.data.name}!`; // Automatically added to responses
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Event Emission with Timeout
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// Basic emission
|
|
174
|
+
const event = await pulse.emit('topic', { message: 'Hello World' });
|
|
175
|
+
|
|
176
|
+
// With custom timeout (default is 5000ms)
|
|
177
|
+
const event = await pulse.emit('topic', data, { timeout: 10000 });
|
|
178
|
+
|
|
179
|
+
// Silent mode (no responses or errors collected)
|
|
180
|
+
const event = await pulse.emit('topic', data, { silent: true });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Handling Responses
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
// In listener: set a response
|
|
187
|
+
pulse.on('greeting', ({ event }) => {
|
|
188
|
+
return `Hello, ${event.data.name}!`; // Automatically added to responses
|
|
189
|
+
// Or explicitly:
|
|
190
|
+
// event.respond(`Hello, ${event.data.name}!`);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// In emitter: get the responses
|
|
194
|
+
const event = await pulse.emit('greeting', { name: 'John' });
|
|
195
|
+
console.log(event.responses[0]); // "Hello, John!"
|
|
196
|
+
|
|
197
|
+
// Multiple listeners can add multiple responses
|
|
198
|
+
pulse.on('greeting', ({ event }) => event.respond('Response 1'));
|
|
199
|
+
pulse.on('greeting', ({ event }) => event.respond('Response 2'));
|
|
200
|
+
const event = await pulse.emit('greeting', {});
|
|
201
|
+
console.log(event.responses); // ['Response 1', 'Response 2']
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Error Handling
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// In listener: handle errors
|
|
208
|
+
pulse.on('process', ({ event }) => {
|
|
209
|
+
try {
|
|
210
|
+
// Do something that might fail
|
|
211
|
+
throw new Error('Something went wrong');
|
|
212
|
+
} catch (err) {
|
|
213
|
+
event.error(err);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Errors thrown in listeners are automatically caught
|
|
218
|
+
pulse.on('process', ({ event }) => {
|
|
219
|
+
throw new Error('Auto-caught error'); // Automatically added to errors
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// In emitter: check for errors
|
|
223
|
+
const event = await pulse.emit('process', data);
|
|
224
|
+
if (event.errors.length > 0) {
|
|
225
|
+
console.error('Errors occurred:', event.errors);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Middleware Chains
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Authentication middleware
|
|
233
|
+
pulse.use('secure:**', async ({ event, pulse, listener }, next) => {
|
|
234
|
+
if (!event.data.token) {
|
|
235
|
+
event.error(new Error('Authentication required'));
|
|
236
|
+
return; // Don't call next() to stop the chain
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Validate token
|
|
240
|
+
const user = validateToken(event.data.token);
|
|
241
|
+
if (!user) {
|
|
242
|
+
event.error(new Error('Invalid token'));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add user to event data for downstream handlers
|
|
247
|
+
event.data.user = user;
|
|
248
|
+
|
|
249
|
+
// Continue the middleware chain
|
|
250
|
+
await next();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Logging middleware
|
|
254
|
+
pulse.use('**', async ({ event }, next) => {
|
|
255
|
+
console.time(`event:${event.id}`);
|
|
256
|
+
await next();
|
|
257
|
+
console.timeEnd(`event:${event.id}`);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Error handling in middleware (v2.1.3+)
|
|
261
|
+
// Errors thrown in middleware are automatically caught and collected
|
|
262
|
+
pulse.use('user:**', async ({ event }, next) => {
|
|
263
|
+
throw new Error('Middleware error'); // Auto-caught and added to event.errors
|
|
264
|
+
// The next middleware and listener will still execute
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Cleaning Up
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
// Remove a specific listener
|
|
272
|
+
const listener = pulse.on('topic', callback);
|
|
273
|
+
listener.destroy();
|
|
274
|
+
|
|
275
|
+
// Remove all listeners for a topic
|
|
276
|
+
pulse.off('topic');
|
|
277
|
+
|
|
278
|
+
// Remove all listeners
|
|
279
|
+
pulse.removeAllListeners();
|
|
280
|
+
|
|
281
|
+
// Clear pattern cache (v2.1.3+)
|
|
282
|
+
// Useful for long-running applications with dynamic patterns
|
|
283
|
+
pulse.clearPatternCache();
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## API Reference
|
|
287
|
+
|
|
288
|
+
### Pulse Class
|
|
289
|
+
|
|
290
|
+
#### Constructor
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const pulse = new Pulse(options);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Options:**
|
|
297
|
+
- `EventClass` (optional): Custom event class that extends PulseEvent
|
|
298
|
+
|
|
299
|
+
#### Methods
|
|
300
|
+
|
|
301
|
+
| Method | Parameters | Returns | Description |
|
|
302
|
+
|--------|------------|---------|-------------|
|
|
303
|
+
| `on` | `pattern: string`, `callback: Function`, `options?: Object` | `Listener` | Subscribe to events matching the pattern |
|
|
304
|
+
| `once` | `pattern: string`, `callback: Function`, `options?: Object` | `Listener` | Subscribe to events matching the pattern (auto-remove after first call) |
|
|
305
|
+
| `use` | `pattern: string`, `callback: Function` | `Middleware` | Add middleware for events matching the pattern |
|
|
306
|
+
| `emit` | `topic: string`, `data: any`, `options?: Object` | `Promise<PulseEvent>` | Emit an event with the specified topic and data |
|
|
307
|
+
| `off` | `pattern: string` | `void` | Remove all listeners for a pattern |
|
|
308
|
+
| `removeAllListeners` | | `void` | Remove all listeners |
|
|
309
|
+
| `clearPatternCache` | | `void` | Clear the pattern cache (useful for memory management) |
|
|
310
|
+
| `matchesPattern` | `topic: string`, `pattern: string` | `boolean` | Check if a topic matches a pattern |
|
|
311
|
+
|
|
312
|
+
**Callback signature for listeners:**
|
|
313
|
+
```javascript
|
|
314
|
+
({ event, pulse, listener }) => { /* ... */ }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Callback signature for middleware:**
|
|
318
|
+
```javascript
|
|
319
|
+
async ({ event, pulse, listener }, next) => { /* ... */ }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Listener Options
|
|
323
|
+
|
|
324
|
+
| Option | Type | Default | Description |
|
|
325
|
+
|--------|------|---------|-------------|
|
|
326
|
+
| `once` | `boolean` | `false` | If true, the listener will be removed after it is called once |
|
|
327
|
+
| `autodestroy.calls` | `number` | `undefined` | Number of calls after which the listener is removed |
|
|
328
|
+
| `autodestroy.timeout` | `number` | `undefined` | Time in milliseconds after which the listener is removed |
|
|
329
|
+
|
|
330
|
+
#### Emit Options
|
|
331
|
+
|
|
332
|
+
| Option | Type | Default | Description |
|
|
333
|
+
|--------|------|---------|-------------|
|
|
334
|
+
| `timeout` | `number` | `5000` | Time in milliseconds to wait for listener responses before timing out |
|
|
335
|
+
| `silent` | `boolean` | `false` | If true, the event will not collect responses or errors |
|
|
336
|
+
| `source` | `string\|null` | `null` | Optional source identifier for the event |
|
|
337
|
+
|
|
338
|
+
### Event Class
|
|
339
|
+
|
|
340
|
+
#### Properties
|
|
341
|
+
|
|
342
|
+
| Property | Type | Description |
|
|
343
|
+
|----------|------|-------------|
|
|
344
|
+
| `topic` | `string` | The topic of the event |
|
|
345
|
+
| `data` | `any` | The payload data of the event |
|
|
346
|
+
| `responses` | `any[]` | Array of response data from listeners |
|
|
347
|
+
| `errors` | `Error[]` | Array of errors that occurred during processing |
|
|
348
|
+
| `timestamp` | `number` | When the event was created (milliseconds since epoch) |
|
|
349
|
+
| `id` | `string` | Unique event identifier |
|
|
350
|
+
| `options` | `Object` | Event options (silent, source, timeout) |
|
|
351
|
+
|
|
352
|
+
#### Methods
|
|
353
|
+
|
|
354
|
+
| Method | Parameters | Returns | Description |
|
|
355
|
+
|--------|------------|---------|-------------|
|
|
356
|
+
| `respond` | `data: any` | `PulseEvent` | Add response data to the event |
|
|
357
|
+
| `error` | `error: Error` | `PulseEvent` | Add an error to the event |
|
|
358
|
+
| `set` | `key: any`, `value: any` | `PulseEvent` | Set a context value (chainable) |
|
|
359
|
+
| `get` | `key: any` | `any` | Get a context value |
|
|
360
|
+
| `has` | `key: any` | `boolean` | Check if a context key exists |
|
|
361
|
+
| `delete` | `key: any` | `boolean` | Delete a context entry |
|
|
362
|
+
| `clearContext` | | `PulseEvent` | Clear all context data (chainable) |
|
|
363
|
+
|
|
364
|
+
## Advanced Examples
|
|
365
|
+
|
|
366
|
+
### Using Event Context
|
|
367
|
+
|
|
368
|
+
Event context allows you to store metadata and share data between middlewares and listeners:
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// Middleware can enrich the event with context data
|
|
372
|
+
pulse.use('user:**', async ({ event }, next) => {
|
|
373
|
+
// Add metadata to event context
|
|
374
|
+
event.set('processingStarted', Date.now());
|
|
375
|
+
event.set('correlationId', generateId());
|
|
376
|
+
|
|
377
|
+
await next();
|
|
378
|
+
|
|
379
|
+
// Access context after processing
|
|
380
|
+
const duration = Date.now() - event.get('processingStarted');
|
|
381
|
+
console.log(`Event ${event.get('correlationId')} took ${duration}ms`);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Listeners can access context data
|
|
385
|
+
pulse.on('user:created', ({ event }) => {
|
|
386
|
+
console.log(`Correlation ID: ${event.get('correlationId')}`);
|
|
387
|
+
|
|
388
|
+
// Store additional metadata
|
|
389
|
+
event.set('handler', 'user-creation-handler');
|
|
390
|
+
event.set('version', '1.0');
|
|
391
|
+
|
|
392
|
+
// Check if context exists
|
|
393
|
+
if (event.has('correlationId')) {
|
|
394
|
+
// Use it for logging/tracking
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Clear context when needed
|
|
399
|
+
pulse.on('cleanup', ({ event }) => {
|
|
400
|
+
event.clearContext();
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Creating a Request-Response Pattern
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
// Create an RPC-like system
|
|
408
|
+
async function callRemote(method, params) {
|
|
409
|
+
const responseTopic = `response:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
|
410
|
+
|
|
411
|
+
// Create a promise that will resolve when we get a response
|
|
412
|
+
const responsePromise = new Promise((resolve) => {
|
|
413
|
+
pulse.once(responseTopic, ({ event }) => {
|
|
414
|
+
resolve(event.data);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Add the response topic to the request
|
|
419
|
+
await pulse.emit(`rpc:${method}`, {
|
|
420
|
+
params,
|
|
421
|
+
responseTopic
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Wait for the response
|
|
425
|
+
return responsePromise;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Handle RPC requests
|
|
429
|
+
pulse.on('rpc:**', ({ event }) => {
|
|
430
|
+
const method = event.topic.split(':')[1];
|
|
431
|
+
const { params, responseTopic } = event.data;
|
|
432
|
+
|
|
433
|
+
// Process the request
|
|
434
|
+
const result = processMethod(method, params);
|
|
435
|
+
|
|
436
|
+
// Send the response back
|
|
437
|
+
pulse.emit(responseTopic, result);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Usage
|
|
441
|
+
const result = await callRemote('getUserProfile', { id: 123 });
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Creating a State Management System
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
class Store {
|
|
448
|
+
constructor(pulse, initialState = {}) {
|
|
449
|
+
this.pulse = pulse;
|
|
450
|
+
this.state = initialState;
|
|
451
|
+
|
|
452
|
+
// Listen for state change requests
|
|
453
|
+
this.pulse.on('store:set:**', ({ event }) => {
|
|
454
|
+
const path = event.topic.replace('store:set:', '').split(':');
|
|
455
|
+
this.setState(path, event.data);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
setState(path, value) {
|
|
460
|
+
let current = this.state;
|
|
461
|
+
const pathArr = Array.isArray(path) ? path : [path];
|
|
462
|
+
|
|
463
|
+
// Navigate to the nested property
|
|
464
|
+
for (let i = 0; i < pathArr.length - 1; i++) {
|
|
465
|
+
if (!current[pathArr[i]]) {
|
|
466
|
+
current[pathArr[i]] = {};
|
|
467
|
+
}
|
|
468
|
+
current = current[pathArr[i]];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Set the value
|
|
472
|
+
const lastKey = pathArr[pathArr.length - 1];
|
|
473
|
+
const oldValue = current[lastKey];
|
|
474
|
+
current[lastKey] = value;
|
|
475
|
+
|
|
476
|
+
// Emit state change event
|
|
477
|
+
this.pulse.emit(`store:changed:${path.join(':')}`, {
|
|
478
|
+
oldValue,
|
|
479
|
+
newValue: value,
|
|
480
|
+
path
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
getState(path) {
|
|
485
|
+
if (!path) return this.state;
|
|
486
|
+
|
|
487
|
+
let current = this.state;
|
|
488
|
+
const pathArr = Array.isArray(path) ? path : path.split(':');
|
|
489
|
+
|
|
490
|
+
for (const key of pathArr) {
|
|
491
|
+
if (current[key] === undefined) return undefined;
|
|
492
|
+
current = current[key];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return current;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Usage
|
|
500
|
+
const store = new Store(pulse, {
|
|
501
|
+
user: { name: 'John', age: 30 },
|
|
502
|
+
settings: { theme: 'dark' }
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Listen for changes
|
|
506
|
+
pulse.on('store:changed:user:name', ({ event }) => {
|
|
507
|
+
console.log(`User's name changed from ${event.data.oldValue} to ${event.data.newValue}`);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Update state
|
|
511
|
+
pulse.emit('store:set:user:name', 'Jane');
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Performance Considerations
|
|
515
|
+
|
|
516
|
+
- **Wildcards**: Using many `**` wildcards can impact performance as they require more complex pattern matching
|
|
517
|
+
- **Listener Count**: High numbers of listeners on frequently emitted topics can affect performance
|
|
518
|
+
- **Middleware Complexity**: Complex middleware chains can add overhead to event processing
|
|
519
|
+
|
|
520
|
+
## Browser Support
|
|
521
|
+
|
|
522
|
+
Pulse works in all modern browsers and Node.js environments. It uses ES6 features like Promises, Maps, and arrow functions.
|
|
523
|
+
|
|
524
|
+
## License
|
|
525
|
+
|
|
526
|
+
MIT ยฉ Killian Di Vincenzo
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Contributing
|
|
531
|
+
|
|
532
|
+
Contributions are welcome! Feel free to submit issues and pull requests.
|
|
533
|
+
|
|
534
|
+
1. Fork the repository
|
|
535
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
536
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
537
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
538
|
+
5. Open a Pull Request
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base event class for Pulse event system.
|
|
3
|
+
* Can be extended to create custom event types.
|
|
4
|
+
*/
|
|
5
|
+
export class PulseEvent {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} topic
|
|
8
|
+
* @param {any} data
|
|
9
|
+
* @param {Object} [options]
|
|
10
|
+
* @param {boolean} [options.silent=false] - If true, the event will not collect responses or errors.
|
|
11
|
+
* @param {string|null} [options.source=null] - The source of the event.
|
|
12
|
+
* @param {number} [options.timeout=5000] - The timeout for the event in milliseconds.
|
|
13
|
+
*/
|
|
14
|
+
constructor(topic: string, data: any, options?: {
|
|
15
|
+
silent?: boolean | undefined;
|
|
16
|
+
source?: string | null | undefined;
|
|
17
|
+
timeout?: number | undefined;
|
|
18
|
+
});
|
|
19
|
+
topic: string;
|
|
20
|
+
data: any;
|
|
21
|
+
options: {
|
|
22
|
+
silent: boolean;
|
|
23
|
+
source: string | null;
|
|
24
|
+
timeout: number;
|
|
25
|
+
};
|
|
26
|
+
timestamp: number;
|
|
27
|
+
id: string;
|
|
28
|
+
/**
|
|
29
|
+
* @type {any[]}
|
|
30
|
+
*/
|
|
31
|
+
responses: any[];
|
|
32
|
+
/**
|
|
33
|
+
* @type {Error[]}
|
|
34
|
+
*/
|
|
35
|
+
errors: Error[];
|
|
36
|
+
/**
|
|
37
|
+
* Add a response to the event
|
|
38
|
+
* @template {PulseEvent} T
|
|
39
|
+
* @this {T}
|
|
40
|
+
* @param {any} data
|
|
41
|
+
* @returns {T} Returns this for chaining
|
|
42
|
+
*/
|
|
43
|
+
respond<T extends PulseEvent>(this: T, data: any): T;
|
|
44
|
+
/**
|
|
45
|
+
* Add an error to the event
|
|
46
|
+
* @template {PulseEvent} T
|
|
47
|
+
* @this {T}
|
|
48
|
+
* @param {Error} err
|
|
49
|
+
* @returns {T} Returns this for chaining
|
|
50
|
+
*/
|
|
51
|
+
error<T extends PulseEvent>(this: T, err: Error): T;
|
|
52
|
+
/**
|
|
53
|
+
* Set a context value
|
|
54
|
+
* @template {PulseEvent} T
|
|
55
|
+
* @this {T}
|
|
56
|
+
* @param {any} key - The key to set
|
|
57
|
+
* @param {any} value - The value to associate with the key
|
|
58
|
+
* @returns {T} Returns this for chaining
|
|
59
|
+
*/
|
|
60
|
+
set<T extends PulseEvent>(this: T, key: any, value: any): T;
|
|
61
|
+
/**
|
|
62
|
+
* Get a context value
|
|
63
|
+
* @param {any} key - The key to retrieve
|
|
64
|
+
* @returns {any} The value associated with the key, or undefined if not found
|
|
65
|
+
*/
|
|
66
|
+
get(key: any): any;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a context key exists
|
|
69
|
+
* @param {any} key - The key to check
|
|
70
|
+
* @returns {boolean} True if the key exists in the context
|
|
71
|
+
*/
|
|
72
|
+
has(key: any): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Delete a context entry
|
|
75
|
+
* @param {any} key - The key to delete
|
|
76
|
+
* @returns {boolean} True if the key existed and was deleted, false otherwise
|
|
77
|
+
*/
|
|
78
|
+
delete(key: any): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Clear all context data
|
|
81
|
+
* @template {PulseEvent} T
|
|
82
|
+
* @this {T}
|
|
83
|
+
* @returns {T} Returns this for chaining
|
|
84
|
+
*/
|
|
85
|
+
clearContext<T extends PulseEvent>(this: T): T;
|
|
86
|
+
#private;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/core/event.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH;IAOI;;;;;;;OAOG;IACH,mBAPW,MAAM,QACN,GAAG,YAEX;QAA0B,MAAM;QACF,MAAM;QACX,OAAO;KAClC,EAuBA;IArBG,cAAkB;IAClB,UAAgB;IAChB;gBAPO,OAAO;gBACP,MAAM,GAAC,IAAI;iBACX,MAAM;MAUZ;IAED,kBAA2B;IAC3B,WAA6D;IAE7D;;OAEG;IACH,WAFU,GAAG,EAAE,CAEI;IAEnB;;OAEG;IACH,QAFU,KAAK,EAAE,CAED;IAGpB;;;;;;OAMG;IACH,QAL0B,CAAC,SAAb,UAAW,iBAEd,GAAG,GACD,CAAC,CAMb;IAED;;;;;;OAMG;IACH,MAL0B,CAAC,SAAb,UAAW,gBAEd,KAAK,GACH,CAAC,CAMb;IAED;;;;;;;OAOG;IACH,IAN0B,CAAC,SAAb,UAAW,gBAEd,GAAG,SACH,GAAG,GACD,CAAC,CAKb;IAED;;;;OAIG;IACH,SAHW,GAAG,GACD,GAAG,CAIf;IAED;;;;OAIG;IACH,SAHW,GAAG,GACD,OAAO,CAInB;IAED;;;;OAIG;IACH,YAHW,GAAG,GACD,OAAO,CAInB;IAED;;;;;OAKG;IACH,aAJ0B,CAAC,SAAb,UAAW,YAEZ,CAAC,CAKb;;CACJ"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ListenerOptions
|
|
3
|
+
* @property {boolean} [once] - If true, the listener will be removed after it is called once.
|
|
4
|
+
* @property {Object} [autodestroy]
|
|
5
|
+
* @property {Number} [autodestroy.timeout] - The time in milliseconds to wait before the listener is removed.
|
|
6
|
+
* @property {Number} [autodestroy.calls] - The number of calls to the listener before it is removed.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @template {import('./event').PulseEvent} [TEvent=import('./event').PulseEvent]
|
|
10
|
+
* @typedef {Object} ListenerContext
|
|
11
|
+
* @property {import('./pulse').Pulse} pulse
|
|
12
|
+
* @property {TEvent} event
|
|
13
|
+
* @property {Listener<TEvent>} listener
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @template {import('./event').PulseEvent} [TEvent=import('./event').PulseEvent]
|
|
17
|
+
*/
|
|
18
|
+
export class Listener<TEvent extends import("./event").PulseEvent = import("./event").PulseEvent> {
|
|
19
|
+
/**
|
|
20
|
+
* @param {import('./pulse').Pulse} pulse
|
|
21
|
+
* @param {string} pattern
|
|
22
|
+
* @param {(context: ListenerContext<TEvent>) => void} callback
|
|
23
|
+
* @param {ListenerOptions} options
|
|
24
|
+
*/
|
|
25
|
+
constructor(pulse: import("./pulse").Pulse, pattern: string, callback: (context: ListenerContext<TEvent>) => void, options: ListenerOptions);
|
|
26
|
+
pulse: import("./pulse").Pulse<typeof import("./event").PulseEvent>;
|
|
27
|
+
pattern: string;
|
|
28
|
+
options: ListenerOptions;
|
|
29
|
+
calls: number;
|
|
30
|
+
timeout: number | null;
|
|
31
|
+
/** @param {TEvent} event */
|
|
32
|
+
call: (event: TEvent) => Promise<void>;
|
|
33
|
+
destroy: () => void;
|
|
34
|
+
#private;
|
|
35
|
+
}
|
|
36
|
+
export type ListenerOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* - If true, the listener will be removed after it is called once.
|
|
39
|
+
*/
|
|
40
|
+
once?: boolean | undefined;
|
|
41
|
+
autodestroy?: {
|
|
42
|
+
/**
|
|
43
|
+
* - The time in milliseconds to wait before the listener is removed.
|
|
44
|
+
*/
|
|
45
|
+
timeout?: number | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* - The number of calls to the listener before it is removed.
|
|
48
|
+
*/
|
|
49
|
+
calls?: number | undefined;
|
|
50
|
+
} | undefined;
|
|
51
|
+
};
|
|
52
|
+
export type ListenerContext<TEvent extends import("./event").PulseEvent = import("./event").PulseEvent> = {
|
|
53
|
+
pulse: import("./pulse").Pulse;
|
|
54
|
+
event: TEvent;
|
|
55
|
+
listener: Listener<TEvent>;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/core/listener.js"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;GAMG;AAEH;;GAEG;AACH,sBAF6C,MAAM,SAAtC,OAAQ,SAAS,EAAE,UAAW;IAGvC;;;;;OAKG;IACH,mBALW,OAAO,SAAS,EAAE,KAAK,WACvB,MAAM,YACN,CAAC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,IAAI,WAC1C,eAAe,EAqBzB;IAlBG,oEAAkB;IAClB,gBAAsB;IAEtB,yBAAsB;IAEtB,cAAc;IACd,uBAAmB;IAiBvB,4BAA4B;IAC5B,OAAc,OADF,MACO,mBAajB;IAEF,oBAWC;;CACJ;;;;;;;;;;;;;;;;;4BArE4C,MAAM,SAAtC,OAAQ,SAAS,EAAE,UAAW;WAE7B,OAAO,SAAS,EAAE,KAAK;WACvB,MAAM;cACN,QAAQ,CAAC,MAAM,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./pulse').Pulse} Pulse
|
|
3
|
+
* @typedef {import('./event').PulseEvent} PulseEvent
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @template {PulseEvent} [TEvent=PulseEvent]
|
|
7
|
+
* @callback NextCallback
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @template {PulseEvent} [TEvent=PulseEvent]
|
|
12
|
+
* @callback MiddlewareCallback
|
|
13
|
+
* @param {import('./listener').ListenerContext<TEvent>} context
|
|
14
|
+
* @param {NextCallback<TEvent>} next
|
|
15
|
+
* @returns {Promise<any>}
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @template {PulseEvent} [TEvent=PulseEvent]
|
|
19
|
+
*/
|
|
20
|
+
export class Middleware<TEvent extends PulseEvent = import("./event").PulseEvent> {
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('./pulse').Pulse} pulse
|
|
23
|
+
* @param {String} pattern
|
|
24
|
+
* @param {MiddlewareCallback<TEvent>} callback
|
|
25
|
+
*/
|
|
26
|
+
constructor(pulse: import("./pulse").Pulse, pattern: string, callback: MiddlewareCallback<TEvent>);
|
|
27
|
+
pulse: import("./pulse").Pulse<typeof import("./event").PulseEvent>;
|
|
28
|
+
pattern: string;
|
|
29
|
+
callback: MiddlewareCallback<TEvent>;
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} topic
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
matches(topic: string): boolean;
|
|
35
|
+
destroy(): void;
|
|
36
|
+
}
|
|
37
|
+
export type Pulse = import("./pulse").Pulse;
|
|
38
|
+
export type PulseEvent = import("./event").PulseEvent;
|
|
39
|
+
export type NextCallback<TEvent extends PulseEvent = import("./event").PulseEvent> = () => Promise<void>;
|
|
40
|
+
export type MiddlewareCallback<TEvent extends PulseEvent = import("./event").PulseEvent> = (context: import("./listener").ListenerContext<TEvent>, next: NextCallback<TEvent>) => Promise<any>;
|
|
41
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/core/middleware.js"],"names":[],"mappings":"AAAA;;;EAGE;AAEF;;;;EAIE;AAEF;;;;;;EAME;AAEF;;EAEE;AACF,wBAF0B,MAAM,SAAnB,UAAW;IAGpB;;;;MAIE;IACF,mBAJU,OAAO,SAAS,EAAE,KAAK,6BAEvB,kBAAkB,CAAC,MAAM,CAAC,EAMnC;IAHG,oEAAkB;IAClB,gBAAsB;IACtB,qCAAwB;IAG5B;;;MAGE;IACF,eAHU,MAAM,GACJ,OAAO,CAIlB;IAED,gBAKC;CACJ;oBA/CW,OAAO,SAAS,EAAE,KAAK;yBACvB,OAAO,SAAS,EAAE,UAAU;yBAId,MAAM,SAAnB,UAAW,yCAEZ,OAAO,CAAC,IAAI,CAAC;+BAIC,MAAM,SAAnB,UAAW,6CAEd,OAAO,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,QAC5C,YAAY,CAAC,MAAM,CAAC,KAClB,OAAO,CAAC,GAAG,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template {typeof PulseEvent} [TEventClass=typeof PulseEvent]
|
|
3
|
+
* @typedef {Object} PulseOptions
|
|
4
|
+
* @property {TEventClass} [EventClass] - Custom event class to use (must extend PulseEvent)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @template {typeof PulseEvent} [TEventClass=typeof PulseEvent]
|
|
8
|
+
*/
|
|
9
|
+
export class Pulse<TEventClass extends typeof PulseEvent = typeof PulseEvent> {
|
|
10
|
+
/**
|
|
11
|
+
* @param {PulseOptions<TEventClass>} [options]
|
|
12
|
+
*/
|
|
13
|
+
constructor(options?: PulseOptions<TEventClass>);
|
|
14
|
+
/** @type {Map<string, Set<import('./listener').Listener<InstanceType<TEventClass>>>>} */
|
|
15
|
+
listeners: Map<string, Set<import("./listener").Listener<InstanceType<TEventClass>>>>;
|
|
16
|
+
/** @type {import('./middleware').Middleware<InstanceType<TEventClass>>[]} */
|
|
17
|
+
middlewares: import("./middleware").Middleware<InstanceType<TEventClass>>[];
|
|
18
|
+
EventClass: typeof PulseEvent;
|
|
19
|
+
/**
|
|
20
|
+
* @param {String} pattern
|
|
21
|
+
* @param {(context: import('./listener').ListenerContext<InstanceType<TEventClass>>) => any} callback
|
|
22
|
+
* @param {import('./listener').ListenerOptions} options
|
|
23
|
+
* @returns {import('./listener').Listener<InstanceType<TEventClass>>}
|
|
24
|
+
*/
|
|
25
|
+
on: (pattern: string, callback: (context: import("./listener").ListenerContext<InstanceType<TEventClass>>) => any, options?: import("./listener").ListenerOptions) => import("./listener").Listener<InstanceType<TEventClass>>;
|
|
26
|
+
/**
|
|
27
|
+
* @param {String} pattern
|
|
28
|
+
* @param {(context: import('./listener').ListenerContext<InstanceType<TEventClass>>) => any} callback
|
|
29
|
+
* @param {import('./listener').ListenerOptions} options
|
|
30
|
+
* @returns {import('./listener').Listener<InstanceType<TEventClass>>}
|
|
31
|
+
*/
|
|
32
|
+
once: (pattern: string, callback: (context: import("./listener").ListenerContext<InstanceType<TEventClass>>) => any, options?: import("./listener").ListenerOptions) => import("./listener").Listener<InstanceType<TEventClass>>;
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} pattern
|
|
35
|
+
* @param {import('./middleware').MiddlewareCallback<InstanceType<TEventClass>>} callback
|
|
36
|
+
* @returns {import('./middleware').Middleware<InstanceType<TEventClass>>}
|
|
37
|
+
*/
|
|
38
|
+
use: (pattern: string, callback: import("./middleware").MiddlewareCallback<InstanceType<TEventClass>>) => import("./middleware").Middleware<InstanceType<TEventClass>>;
|
|
39
|
+
/**
|
|
40
|
+
* @param {InstanceType<TEventClass>} event
|
|
41
|
+
* @param {import('./listener').Listener<InstanceType<TEventClass>>} listener
|
|
42
|
+
*/
|
|
43
|
+
applyMiddlewaresToListener(event: InstanceType<TEventClass>, listener: import("./listener").Listener<InstanceType<TEventClass>>): Promise<any>;
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} topic
|
|
46
|
+
* @param {any} data
|
|
47
|
+
* @param {{
|
|
48
|
+
* timeout?: number,
|
|
49
|
+
* }} options
|
|
50
|
+
* @returns {Promise<InstanceType<TEventClass>>}
|
|
51
|
+
*/
|
|
52
|
+
emit: (topic: string, data: any, options?: {
|
|
53
|
+
timeout?: number;
|
|
54
|
+
}) => Promise<InstanceType<TEventClass>>;
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} topic
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
isValidTopic(topic: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} topic
|
|
62
|
+
* @param {string} pattern
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
matchesPattern(topic: string, pattern: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Remove all listeners for a specific pattern
|
|
68
|
+
* @param {string} pattern
|
|
69
|
+
*/
|
|
70
|
+
off(pattern: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Remove all listeners
|
|
73
|
+
*/
|
|
74
|
+
removeAllListeners(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Clear the pattern cache
|
|
77
|
+
* Useful for memory management in long-running applications
|
|
78
|
+
*/
|
|
79
|
+
clearPatternCache(): void;
|
|
80
|
+
#private;
|
|
81
|
+
}
|
|
82
|
+
export type PulseOptions<TEventClass extends typeof PulseEvent = typeof PulseEvent> = {
|
|
83
|
+
/**
|
|
84
|
+
* - Custom event class to use (must extend PulseEvent)
|
|
85
|
+
*/
|
|
86
|
+
EventClass?: TEventClass | undefined;
|
|
87
|
+
};
|
|
88
|
+
import { PulseEvent } from './event.js';
|
|
89
|
+
//# sourceMappingURL=pulse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulse.d.ts","sourceRoot":"","sources":["../../src/core/pulse.js"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH;;GAEG;AACH,mBAFkC,WAAW,SAAhC,OAAQ,UAAW;IAG5B;;OAEG;IACH,sBAFW,YAAY,CAAC,WAAW,CAAC,EAiBnC;IAdG,yFAAyF;IACzF,WADW,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAC3D;IAC1B,6EAA6E;IAC7E,aADW,OAAO,cAAc,EAAE,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,EAAE,CACpD;IAKrB,8BAAkD;IActD;;;;;OAKG;IACH,KAAM,eAAO,EAAE,UAJJ,CAAC,OAAO,EAAE,OAAO,YAAY,EAAE,eAAe,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,KAAK,GAIlE,EAAE,UAHd,OAAO,YAAY,EAAE,eAGK,KAFxB,OAAO,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CASpE;IAED;;;;;OAKG;IACH,OAAQ,eAAO,EAAE,UAJN,CAAC,OAAO,EAAE,OAAO,YAAY,EAAE,eAAe,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,KAAK,GAIhE,EAAE,UAHhB,OAAO,YAAY,EAAE,eAGO,KAF1B,OAAO,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAE6B;IAElG;;;;OAIG;IACH,MAAO,SAJI,MAIG,EAAE,UAHL,OAAO,cAAc,EAAE,kBAAkB,CAAC,YAAY,CAAC,WAAW,CAAC,CAGtD,KAFX,OAAO,cAAc,EAAE,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAMxE;IAED;;;OAGG;IACH,kCAHW,YAAY,CAAC,WAAW,CAAC,YACzB,OAAO,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,gBA2BlE;IAED;;;;;;;OAOG;IACH,OAAc,OAPH,MAOQ,EAAE,MANV,GAMc,EAAE,UALhB;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;KAImB,KAF1B,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CA+C9C;IA0HD;;;OAGG;IACH,oBAHW,MAAM,GACJ,OAAO,CAKnB;IAED;;;;OAIG;IACH,sBAJW,MAAM,WACN,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,aAFW,MAAM,QAIhB;IAED;;OAEG;IACH,2BAEC;IAED;;;OAGG;IACH,0BAEC;;CAEJ;yBA5TiC,WAAW,SAAhC,OAAQ,UAAW;;;;;;2BALL,YAAY"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var R=0;class G{#f=new Map;constructor(f,q,z={}){this.topic=f,this.data=q,this.options={silent:!1,source:null,timeout:5000,...z},this.timestamp=Date.now(),this.id=`${this.topic}-${this.timestamp}-${++R}`,this.responses=[],this.errors=[]}respond(f){if(this.options.silent)return this;return this.responses.push(f),this}error(f){if(this.options.silent)return this;return this.errors.push(f),this}set(f,q){return this.#f.set(f,q),this}get(f){return this.#f.get(f)}has(f){return this.#f.has(f)}delete(f){return this.#f.delete(f)}clearContext(){return this.#f.clear(),this}}class Y{constructor(f,q,z,B){if(this.pulse=f,this.pattern=q,this.#f=z,this.options=B,this.calls=0,this.timeout=null,this.options?.autodestroy?.timeout)this.timeout=setTimeout(()=>{this.destroy()},this.options.autodestroy.timeout);if(this.options?.once)this.options.autodestroy??={},this.options.autodestroy.calls=1}#f;call=async(f)=>{if(this.calls++,this.options?.autodestroy?.calls&&this.calls>=this.options.autodestroy.calls)this.destroy();return Promise.resolve(this.#f({event:f,pulse:this.pulse,listener:this})).then((q)=>{if(q!==void 0&&q!==null)f.respond(q)}).catch((q)=>{f.error(q)})};destroy=()=>{if(this.timeout)clearTimeout(this.timeout);let f=this.pulse.listeners.get(this.pattern);if(f){if(f.delete(this),f.size===0)this.pulse.listeners.delete(this.pattern)}}}class ${constructor(f,q,z){this.pulse=f,this.pattern=q,this.callback=z}matches(f){return this.pulse.matchesPattern(f,this.pattern)}destroy(){this.pulse.middlewares=this.pulse.middlewares.filter((f)=>f!==this),this.destroy=()=>{throw Error("Middleware already destroyed")}}}class K{constructor(f={}){if(this.listeners=new Map,this.middlewares=[],this.#f=new Map,this.EventClass=f.EventClass||G,this.EventClass!==G&&!(this.EventClass.prototype instanceof G))throw Error("EventClass must extend PulseEvent")}#f;#z=1000;on=(f,q,z={})=>{let B=new Y(this,f,q,z),D=f.replace(/\*+|\++/g,"placeholder");if(!this.isValidTopic(D))throw Error(`Invalid pattern: ${f}`);if(!this.listeners.has(f))this.listeners.set(f,new Set);return this.listeners.get(f)?.add(B),B};once=(f,q,z={})=>this.on(f,q,{...z,once:!0});use=(f,q)=>{let z=new $(this,f,q);return this.middlewares.push(z),z};async applyMiddlewaresToListener(f,q){let z=this.middlewares.filter((F)=>F.matches(f.topic));if(z.length===0)return q.call(f);let B=0,D=async()=>{if(B>=z.length)return q.call(f);let F=z[B++];if(!F)return q.call(f);try{return await F.callback({event:f,pulse:this,listener:q},D)}catch(J){let N=J instanceof Error?J:Error(String(J));f.error(N)}};return D()}emit=async(f,q,z={})=>{if(!this.isValidTopic(f))throw Error(`Invalid topic: ${f}`);let B=[];for(let[N,V]of this.listeners.entries())if(this.#q(f,N))B.push(...V);let D=new this.EventClass(f,q,z);if(B.length===0)return D;let F=z?.timeout||5000,J=B.map(async(N)=>{let V;try{let Q=new Promise((H,L)=>{V=setTimeout(()=>{L(Error(`Listener timed out after ${F}ms for topic: ${f}`))},F)});return await Promise.race([this.applyMiddlewaresToListener(D,N),Q])}catch(Q){let H=Q instanceof Error?Q:Error(String(Q));D.error(H)}finally{clearTimeout(V)}});return await Promise.allSettled(J),D};#B(f){if(this.#f.has(f))return this.#f.get(f)||/.*/;let q="^",z=0;while(z<f.length)if(f[z]==="*"&&f[z+1]==="*"){if(z===0){if(z+2>=f.length)q+=".*";else if(f[z+2]===":"){q+="(?:.*:)?",z+=3;continue}}else{if(q.endsWith("\\:"))q=q.slice(0,-2);if(z+2>=f.length)q+="(?::[^:]+(?::[^:]+)*)?";else if(f[z+2]===":"){q+="(?::[^:]+(?::[^:]+)*)?\\:",z+=3;continue}}z+=2}else if(f[z]==="+"&&f[z+1]==="+"){if(z===0){if(z+2>=f.length)q+="[^:]+(?::[^:]+)*";else if(f[z+2]===":"){q+="[^:]+(?::[^:]+)*\\:",z+=3;continue}}else{if(q.endsWith("\\:"))q=q.slice(0,-2);if(z+2>=f.length)q+=":[^:]+(?::[^:]+)*";else if(f[z+2]===":"){q+=":[^:]+(?::[^:]+)*\\:",z+=3;continue}}z+=2}else if(f[z]==="*")q+="[^:]+",z++;else if(f[z]===":"||f[z]===".")q+="\\"+f[z],z++;else q+=f[z],z++;q+="$";let B=new RegExp(q);if(this.#f.size>=this.#z){let D=this.#f.keys().next().value;this.#f.delete(D)}return this.#f.set(f,B),B}#q(f,q){return this.#B(q).test(f)}isValidTopic(f){return/^[a-zA-Z0-9_-]+(?::[a-zA-Z0-9_-]+)*$/.test(f)}matchesPattern(f,q){return this.#q(f,q)}off(f){this.listeners.delete(f)}removeAllListeners(){this.listeners.clear()}clearPatternCache(){this.#f.clear()}}export{G as PulseEvent,K as Pulse,$ as Middleware,Y as Listener};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aionbuilders/pulse",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A powerful, lightweight event system with pattern matching, middleware, and timeout support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"events",
|
|
14
|
+
"pubsub",
|
|
15
|
+
"middleware",
|
|
16
|
+
"timeout",
|
|
17
|
+
"pattern-matching"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"demo": "bun --watch test/demo.js",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"build": "bun build src/index.js --outdir dist --target node --minify",
|
|
23
|
+
"generate-types": "tsc --project ./tsc/tsconfig.json",
|
|
24
|
+
"prepublishOnly": "npm run clean && npm run build && npm run generate-types",
|
|
25
|
+
"release:alpha": "npm version prerelease && npm publish --tag alpha",
|
|
26
|
+
"release:stable": "npm version major && npm publish",
|
|
27
|
+
"release:patch": "npm version patch && npm publish",
|
|
28
|
+
"release:minor": "npm version minor && npm publish --tag latest"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"author": "Killian Di Vincenzo",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/aionbuilders/pulse.git"
|
|
38
|
+
},
|
|
39
|
+
"type": "module",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
}
|