evstream 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/redis.d.ts +10 -0
- package/dist/adapters/redis.js +70 -0
- package/dist/manager.js +25 -11
- package/dist/state.d.ts +1 -1
- package/dist/state.js +27 -3
- package/dist/stream.d.ts +1 -0
- package/dist/stream.js +25 -7
- package/dist/types.d.ts +6 -0
- package/package.json +60 -47
- package/readme.md +54 -5
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RedisOptions } from 'ioredis';
|
|
2
|
+
import { EvStateAdapter } from '../types.js';
|
|
3
|
+
export declare class EvRedisAdapter implements EvStateAdapter {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(options?: RedisOptions);
|
|
6
|
+
publish(channel: string, message: any): Promise<void>;
|
|
7
|
+
subscribe(channel: string, onMessage: (message: any) => void): Promise<void>;
|
|
8
|
+
unsubscribe(channel: string): Promise<void>;
|
|
9
|
+
quit(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
11
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
14
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
15
|
+
};
|
|
16
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
17
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
18
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
19
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
20
|
+
};
|
|
21
|
+
var _EvRedisAdapter_pub, _EvRedisAdapter_sub, _EvRedisAdapter_listeners;
|
|
22
|
+
import Redis from 'ioredis';
|
|
23
|
+
export class EvRedisAdapter {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
_EvRedisAdapter_pub.set(this, void 0);
|
|
26
|
+
_EvRedisAdapter_sub.set(this, void 0);
|
|
27
|
+
_EvRedisAdapter_listeners.set(this, void 0);
|
|
28
|
+
__classPrivateFieldSet(this, _EvRedisAdapter_pub, new Redis(options), "f");
|
|
29
|
+
__classPrivateFieldSet(this, _EvRedisAdapter_sub, new Redis(options), "f");
|
|
30
|
+
__classPrivateFieldSet(this, _EvRedisAdapter_listeners, new Map(), "f");
|
|
31
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").on('message', (channel, message) => {
|
|
32
|
+
const handlers = __classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").get(channel);
|
|
33
|
+
if (handlers) {
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(message);
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
handlers.forEach(handler => handler(parsed));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
publish(channel, message) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
yield __classPrivateFieldGet(this, _EvRedisAdapter_pub, "f").publish(channel, JSON.stringify(message));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
subscribe(channel, onMessage) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
if (!__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").has(channel)) {
|
|
53
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").set(channel, new Set());
|
|
54
|
+
yield __classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").subscribe(channel);
|
|
55
|
+
}
|
|
56
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").get(channel).add(onMessage);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
unsubscribe(channel) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
yield __classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").unsubscribe(channel);
|
|
62
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").delete(channel);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
quit() {
|
|
66
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_pub, "f").quit();
|
|
67
|
+
__classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").quit();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
_EvRedisAdapter_pub = new WeakMap(), _EvRedisAdapter_sub = new WeakMap(), _EvRedisAdapter_listeners = new WeakMap();
|
package/dist/manager.js
CHANGED
|
@@ -63,17 +63,29 @@ export class EvStreamManager {
|
|
|
63
63
|
onClose(channel);
|
|
64
64
|
}
|
|
65
65
|
isClosed = true;
|
|
66
|
+
// Remove close event listener to prevent memory leaks
|
|
67
|
+
res.removeAllListeners('close');
|
|
68
|
+
// Clean up client
|
|
66
69
|
client.close();
|
|
70
|
+
// Decrement count
|
|
67
71
|
__classPrivateFieldSet(this, _EvStreamManager_count, __classPrivateFieldGet(this, _EvStreamManager_count, "f") - 1, "f");
|
|
72
|
+
// Remove from all channels
|
|
68
73
|
channel.forEach(chan => __classPrivateFieldGet(this, _EvStreamManager_instances, "m", _EvStreamManager_unlisten).call(this, chan, id));
|
|
74
|
+
// Clear channel array to release references
|
|
75
|
+
channel.length = 0;
|
|
76
|
+
// Remove client from map
|
|
69
77
|
__classPrivateFieldGet(this, _EvStreamManager_clients, "f").delete(id);
|
|
70
|
-
|
|
78
|
+
// End response if not already ended
|
|
79
|
+
if (!res.writableEnded) {
|
|
80
|
+
res.end();
|
|
81
|
+
}
|
|
71
82
|
};
|
|
72
|
-
|
|
83
|
+
const onCloseHandler = () => {
|
|
73
84
|
if (!isClosed) {
|
|
74
85
|
close();
|
|
75
86
|
}
|
|
76
|
-
}
|
|
87
|
+
};
|
|
88
|
+
res.on('close', onCloseHandler);
|
|
77
89
|
return {
|
|
78
90
|
authenticate: client.authenticate.bind(client),
|
|
79
91
|
message: client.message.bind(client),
|
|
@@ -91,6 +103,8 @@ export class EvStreamManager {
|
|
|
91
103
|
*/
|
|
92
104
|
send(name, msg) {
|
|
93
105
|
const listeners = __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").get(name);
|
|
106
|
+
if (!listeners)
|
|
107
|
+
return;
|
|
94
108
|
for (const [_, id] of listeners.entries()) {
|
|
95
109
|
const client = __classPrivateFieldGet(this, _EvStreamManager_clients, "f").get(id);
|
|
96
110
|
if (client) {
|
|
@@ -102,15 +116,15 @@ export class EvStreamManager {
|
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
_EvStreamManager_clients = new WeakMap(), _EvStreamManager_listeners = new WeakMap(), _EvStreamManager_count = new WeakMap(), _EvStreamManager_maxConnections = new WeakMap(), _EvStreamManager_maxListeners = new WeakMap(), _EvStreamManager_id = new WeakMap(), _EvStreamManager_instances = new WeakSet(), _EvStreamManager_listen = function _EvStreamManager_listen(name, id) {
|
|
105
|
-
|
|
106
|
-
if (!
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
let listeners = __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").get(name);
|
|
120
|
+
if (!listeners) {
|
|
121
|
+
listeners = new Set();
|
|
122
|
+
__classPrivateFieldGet(this, _EvStreamManager_listeners, "f").set(name, listeners);
|
|
123
|
+
}
|
|
124
|
+
if (listeners.size >= __classPrivateFieldGet(this, _EvStreamManager_maxListeners, "f")) {
|
|
125
|
+
throw new EvMaxListenerError(listeners.size, name);
|
|
112
126
|
}
|
|
113
|
-
|
|
127
|
+
listeners.add(id);
|
|
114
128
|
}, _EvStreamManager_unlisten = function _EvStreamManager_unlisten(name, id) {
|
|
115
129
|
const isListenerExists = __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").get(name);
|
|
116
130
|
if (isListenerExists) {
|
package/dist/state.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ type EvSetState<T> = (val: T) => T;
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class EvState<T> {
|
|
7
7
|
#private;
|
|
8
|
-
constructor({ channel, initialValue, manager, key }: EvStateOptions<T>);
|
|
8
|
+
constructor({ channel, initialValue, manager, key, adapter }: EvStateOptions<T>);
|
|
9
9
|
/**
|
|
10
10
|
* Returns the current state value.
|
|
11
11
|
*/
|
package/dist/state.js
CHANGED
|
@@ -9,22 +9,30 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _EvState_value, _EvState_channel, _EvState_manager, _EvState_key;
|
|
12
|
+
var _EvState_instances, _EvState_value, _EvState_channel, _EvState_manager, _EvState_key, _EvState_adapter, _EvState_handleRemoteUpdate;
|
|
13
13
|
import loadash from 'lodash';
|
|
14
14
|
const { isEqual } = loadash;
|
|
15
15
|
/**
|
|
16
16
|
* EvState holds a reactive state and broadcasts updates to a channel using EvStreamManager.
|
|
17
17
|
*/
|
|
18
18
|
export class EvState {
|
|
19
|
-
constructor({ channel, initialValue, manager, key }) {
|
|
19
|
+
constructor({ channel, initialValue, manager, key, adapter }) {
|
|
20
|
+
_EvState_instances.add(this);
|
|
20
21
|
_EvState_value.set(this, void 0);
|
|
21
22
|
_EvState_channel.set(this, void 0);
|
|
22
23
|
_EvState_manager.set(this, void 0);
|
|
23
24
|
_EvState_key.set(this, void 0);
|
|
25
|
+
_EvState_adapter.set(this, void 0);
|
|
24
26
|
__classPrivateFieldSet(this, _EvState_value, initialValue, "f");
|
|
25
27
|
__classPrivateFieldSet(this, _EvState_channel, channel, "f");
|
|
26
28
|
__classPrivateFieldSet(this, _EvState_manager, manager, "f");
|
|
27
29
|
__classPrivateFieldSet(this, _EvState_key, key || 'value', "f");
|
|
30
|
+
__classPrivateFieldSet(this, _EvState_adapter, adapter, "f");
|
|
31
|
+
if (__classPrivateFieldGet(this, _EvState_adapter, "f")) {
|
|
32
|
+
__classPrivateFieldGet(this, _EvState_adapter, "f").subscribe(__classPrivateFieldGet(this, _EvState_channel, "f"), (data) => {
|
|
33
|
+
__classPrivateFieldGet(this, _EvState_instances, "m", _EvState_handleRemoteUpdate).call(this, data);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* Returns the current state value.
|
|
@@ -46,7 +54,23 @@ export class EvState {
|
|
|
46
54
|
[__classPrivateFieldGet(this, _EvState_key, "f")]: newValue,
|
|
47
55
|
},
|
|
48
56
|
});
|
|
57
|
+
if (__classPrivateFieldGet(this, _EvState_adapter, "f")) {
|
|
58
|
+
__classPrivateFieldGet(this, _EvState_adapter, "f").publish(__classPrivateFieldGet(this, _EvState_channel, "f"), { [__classPrivateFieldGet(this, _EvState_key, "f")]: newValue });
|
|
59
|
+
}
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
|
-
_EvState_value = new WeakMap(), _EvState_channel = new WeakMap(), _EvState_manager = new WeakMap(), _EvState_key = new WeakMap()
|
|
63
|
+
_EvState_value = new WeakMap(), _EvState_channel = new WeakMap(), _EvState_manager = new WeakMap(), _EvState_key = new WeakMap(), _EvState_adapter = new WeakMap(), _EvState_instances = new WeakSet(), _EvState_handleRemoteUpdate = function _EvState_handleRemoteUpdate(data) {
|
|
64
|
+
if (data && typeof data === 'object' && __classPrivateFieldGet(this, _EvState_key, "f") in data) {
|
|
65
|
+
const newValue = data[__classPrivateFieldGet(this, _EvState_key, "f")];
|
|
66
|
+
if (!isEqual(newValue, __classPrivateFieldGet(this, _EvState_value, "f"))) {
|
|
67
|
+
__classPrivateFieldSet(this, _EvState_value, newValue, "f");
|
|
68
|
+
__classPrivateFieldGet(this, _EvState_manager, "f").send(__classPrivateFieldGet(this, _EvState_channel, "f"), {
|
|
69
|
+
event: __classPrivateFieldGet(this, _EvState_channel, "f"),
|
|
70
|
+
data: {
|
|
71
|
+
[__classPrivateFieldGet(this, _EvState_key, "f")]: newValue,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
package/dist/stream.d.ts
CHANGED
package/dist/stream.js
CHANGED
|
@@ -18,7 +18,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
18
18
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
19
19
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
20
20
|
};
|
|
21
|
-
var _Evstream_res, _Evstream_opts, _Evstream_url;
|
|
21
|
+
var _Evstream_instances, _Evstream_res, _Evstream_opts, _Evstream_url, _Evstream_heartbeatInterval, _Evstream_onCloseHandler, _Evstream_clearHeartbeat, _Evstream_removeCloseListener;
|
|
22
22
|
import { message } from './message.js';
|
|
23
23
|
/**
|
|
24
24
|
* Evstream manages a Server-Sent Events (SSE) connection.
|
|
@@ -33,9 +33,12 @@ import { message } from './message.js';
|
|
|
33
33
|
*/
|
|
34
34
|
export class Evstream {
|
|
35
35
|
constructor(req, res, opts) {
|
|
36
|
+
_Evstream_instances.add(this);
|
|
36
37
|
_Evstream_res.set(this, void 0);
|
|
37
38
|
_Evstream_opts.set(this, void 0);
|
|
38
39
|
_Evstream_url.set(this, void 0);
|
|
40
|
+
_Evstream_heartbeatInterval.set(this, void 0);
|
|
41
|
+
_Evstream_onCloseHandler.set(this, void 0);
|
|
39
42
|
__classPrivateFieldSet(this, _Evstream_res, res, "f");
|
|
40
43
|
__classPrivateFieldSet(this, _Evstream_opts, opts, "f");
|
|
41
44
|
__classPrivateFieldSet(this, _Evstream_url, new URL(req.url, `http://${req.headers.host}`), "f");
|
|
@@ -44,12 +47,13 @@ export class Evstream {
|
|
|
44
47
|
__classPrivateFieldGet(this, _Evstream_res, "f").setHeader('Connection', 'keep-alive');
|
|
45
48
|
__classPrivateFieldGet(this, _Evstream_res, "f").flushHeaders();
|
|
46
49
|
if (opts === null || opts === void 0 ? void 0 : opts.heartbeat) {
|
|
47
|
-
|
|
50
|
+
__classPrivateFieldSet(this, _Evstream_heartbeatInterval, setInterval(() => {
|
|
48
51
|
__classPrivateFieldGet(this, _Evstream_res, "f").write(message({ event: 'heartbeat', data: '' }));
|
|
49
|
-
}, __classPrivateFieldGet(this, _Evstream_opts, "f").heartbeat);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
52
|
+
}, __classPrivateFieldGet(this, _Evstream_opts, "f").heartbeat), "f");
|
|
53
|
+
__classPrivateFieldSet(this, _Evstream_onCloseHandler, () => {
|
|
54
|
+
__classPrivateFieldGet(this, _Evstream_instances, "m", _Evstream_clearHeartbeat).call(this);
|
|
55
|
+
}, "f");
|
|
56
|
+
__classPrivateFieldGet(this, _Evstream_res, "f").on('close', __classPrivateFieldGet(this, _Evstream_onCloseHandler, "f"));
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
/**
|
|
@@ -63,6 +67,7 @@ export class Evstream {
|
|
|
63
67
|
const isAuthenticated = yield __classPrivateFieldGet(this, _Evstream_opts, "f").authentication.verify(token);
|
|
64
68
|
if (typeof isAuthenticated === 'boolean') {
|
|
65
69
|
if (!isAuthenticated) {
|
|
70
|
+
__classPrivateFieldGet(this, _Evstream_instances, "m", _Evstream_clearHeartbeat).call(this);
|
|
66
71
|
this.message({
|
|
67
72
|
data: { message: 'authentication failed' },
|
|
68
73
|
event: 'error',
|
|
@@ -89,8 +94,11 @@ export class Evstream {
|
|
|
89
94
|
}
|
|
90
95
|
/**
|
|
91
96
|
* Sends an "end" event and closes the SSE connection.
|
|
97
|
+
* Cleans up heartbeat interval and event listeners to prevent memory leaks.
|
|
92
98
|
*/
|
|
93
99
|
close() {
|
|
100
|
+
__classPrivateFieldGet(this, _Evstream_instances, "m", _Evstream_clearHeartbeat).call(this);
|
|
101
|
+
__classPrivateFieldGet(this, _Evstream_instances, "m", _Evstream_removeCloseListener).call(this);
|
|
94
102
|
this.message({
|
|
95
103
|
event: 'end',
|
|
96
104
|
data: '',
|
|
@@ -98,4 +106,14 @@ export class Evstream {
|
|
|
98
106
|
__classPrivateFieldGet(this, _Evstream_res, "f").end();
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
|
-
_Evstream_res = new WeakMap(), _Evstream_opts = new WeakMap(), _Evstream_url = new WeakMap()
|
|
109
|
+
_Evstream_res = new WeakMap(), _Evstream_opts = new WeakMap(), _Evstream_url = new WeakMap(), _Evstream_heartbeatInterval = new WeakMap(), _Evstream_onCloseHandler = new WeakMap(), _Evstream_instances = new WeakSet(), _Evstream_clearHeartbeat = function _Evstream_clearHeartbeat() {
|
|
110
|
+
if (__classPrivateFieldGet(this, _Evstream_heartbeatInterval, "f")) {
|
|
111
|
+
clearInterval(__classPrivateFieldGet(this, _Evstream_heartbeatInterval, "f"));
|
|
112
|
+
__classPrivateFieldSet(this, _Evstream_heartbeatInterval, undefined, "f");
|
|
113
|
+
}
|
|
114
|
+
}, _Evstream_removeCloseListener = function _Evstream_removeCloseListener() {
|
|
115
|
+
if (__classPrivateFieldGet(this, _Evstream_onCloseHandler, "f")) {
|
|
116
|
+
__classPrivateFieldGet(this, _Evstream_res, "f").removeListener('close', __classPrivateFieldGet(this, _Evstream_onCloseHandler, "f"));
|
|
117
|
+
__classPrivateFieldSet(this, _Evstream_onCloseHandler, undefined, "f");
|
|
118
|
+
}
|
|
119
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -19,10 +19,16 @@ export interface EvManagerOptions {
|
|
|
19
19
|
maxConnection?: number;
|
|
20
20
|
maxListeners?: number;
|
|
21
21
|
}
|
|
22
|
+
export interface EvStateAdapter {
|
|
23
|
+
publish(channel: string, message: any): Promise<void>;
|
|
24
|
+
subscribe(channel: string, onMessage: (message: any) => void): Promise<void>;
|
|
25
|
+
unsubscribe(channel: string): Promise<void>;
|
|
26
|
+
}
|
|
22
27
|
export interface EvStateOptions<T> {
|
|
23
28
|
initialValue: T;
|
|
24
29
|
channel: string;
|
|
25
30
|
manager: EvStreamManager;
|
|
26
31
|
key?: string;
|
|
32
|
+
adapter?: EvStateAdapter;
|
|
27
33
|
}
|
|
28
34
|
export type EvOnClose = (channels: string[]) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,47 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "evstream",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A simple and easy to implement server sent event library for express.js",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"sse",
|
|
7
|
-
"server-sent-events",
|
|
8
|
-
"event-source"
|
|
9
|
-
],
|
|
10
|
-
"homepage": "https://github.com/kisshan13/evstream#readme",
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/kisshan13/evstream/issues"
|
|
13
|
-
},
|
|
14
|
-
"repository": {
|
|
15
|
-
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/kisshan13/evstream.git"
|
|
17
|
-
},
|
|
18
|
-
"license": "MIT",
|
|
19
|
-
"author": "Kishan Sharma",
|
|
20
|
-
"type": "module",
|
|
21
|
-
"main": "./dist/index.js",
|
|
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
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "evstream",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A simple and easy to implement server sent event library for express.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sse",
|
|
7
|
+
"server-sent-events",
|
|
8
|
+
"event-source"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/kisshan13/evstream#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/kisshan13/evstream/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/kisshan13/evstream.git"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Kishan Sharma",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./adapter/redis": {
|
|
29
|
+
"import": "./dist/adapters/redis.js",
|
|
30
|
+
"types": "./dist/adapters/redis.d.ts"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "tsc --watch",
|
|
35
|
+
"clean": "rimraf ./dist",
|
|
36
|
+
"build": "rimraf ./dist && tsc --incremental false"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=17.8.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/ioredis": "^5.0.0",
|
|
43
|
+
"@types/lodash": "^4.17.19",
|
|
44
|
+
"@types/node": "^24.0.7",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^8.35.0",
|
|
46
|
+
"@typescript-eslint/parser": "^8.35.0",
|
|
47
|
+
"eslint": "^9.30.0",
|
|
48
|
+
"eslint-config-prettier": "^10.1.5",
|
|
49
|
+
"eslint-plugin-import": "^2.32.0",
|
|
50
|
+
"eslint-plugin-prettier": "^5.5.1",
|
|
51
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
52
|
+
"ioredis": "^5.5.0",
|
|
53
|
+
"prettier": "^3.6.2",
|
|
54
|
+
"rimraf": "^6.0.1",
|
|
55
|
+
"typescript": "^5.8.3"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"lodash": "^4.17.21"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/readme.md
CHANGED
|
@@ -188,7 +188,35 @@ Reactive states are data which you can shared across multiple clients within the
|
|
|
188
188
|
|
|
189
189
|
**See** `channel` **and the value pass to the** `listen()` **must be the same**
|
|
190
190
|
|
|
191
|
-
### 5.
|
|
191
|
+
### 5. Distributed Reactive State (Redis)
|
|
192
|
+
|
|
193
|
+
When running multiple server instances, you can synchronize `EvState` across them using the built-in Redis adapter.
|
|
194
|
+
|
|
195
|
+
1. **Install the peer dependency:**
|
|
196
|
+
```bash
|
|
197
|
+
npm install ioredis
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
2. **Use the adapter:**
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
import { EvState, EvStreamManager } from "evstream"
|
|
204
|
+
import { EvRedisAdapter } from "evstream/adapter/redis"
|
|
205
|
+
|
|
206
|
+
const manager = new EvStreamManager();
|
|
207
|
+
const redisAdapter = new EvRedisAdapter("redis://localhost:6379");
|
|
208
|
+
|
|
209
|
+
const userCount = new EvState({
|
|
210
|
+
channel: "user-count",
|
|
211
|
+
initialValue: 0,
|
|
212
|
+
manager: manager,
|
|
213
|
+
adapter: redisAdapter
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Updates to `userCount` will now be synchronized across all instances connected to the same Redis.
|
|
218
|
+
|
|
219
|
+
### 6. Sending data to a channel
|
|
192
220
|
|
|
193
221
|
To send data to a channel you can use `send()` method from `EvStreamManager` class.
|
|
194
222
|
|
|
@@ -202,7 +230,7 @@ const manager = new EvStreamManager();
|
|
|
202
230
|
manager.send("<channel-name>", {event: "custom-event", data: {"foo": "bar"}})
|
|
203
231
|
```
|
|
204
232
|
|
|
205
|
-
###
|
|
233
|
+
### 7. Listening for channels
|
|
206
234
|
|
|
207
235
|
To listen for data from any channel you can use `listen()` function from `Evstream` class.
|
|
208
236
|
|
|
@@ -368,7 +396,8 @@ new EvState<T>({
|
|
|
368
396
|
channel,
|
|
369
397
|
initialValue,
|
|
370
398
|
manager,
|
|
371
|
-
key
|
|
399
|
+
key,
|
|
400
|
+
adapter
|
|
372
401
|
}: EvStateOptions<T>)
|
|
373
402
|
```
|
|
374
403
|
|
|
@@ -378,6 +407,7 @@ new EvState<T>({
|
|
|
378
407
|
* `initialValue`: `T` – The initial state value.
|
|
379
408
|
* `manager`: `EvStreamManager` – The SSE manager instance used for broadcasting.
|
|
380
409
|
* `key` *(optional)*: `string` – The key used in the broadcasted data object (default: `'value'`).
|
|
410
|
+
* `adapter` *(optional)*: `EvStateAdapter` – Adapter for distributed state synchronization (e.g. `EvRedisAdapter`).
|
|
381
411
|
|
|
382
412
|
---
|
|
383
413
|
|
|
@@ -437,13 +467,31 @@ new EvMaxConnectionsError(connections: number)
|
|
|
437
467
|
```ts
|
|
438
468
|
const manager = new EvStreamManager({ maxConnection: 100 });
|
|
439
469
|
if (tooManyConnections) {
|
|
470
|
+
```
|
|
471
|
+
|
|
440
472
|
throw new EvMaxConnectionsError(100)
|
|
441
473
|
}
|
|
442
474
|
```
|
|
443
475
|
|
|
444
476
|
---
|
|
445
|
-
|
|
446
|
-
## `
|
|
477
|
+
|
|
478
|
+
## `EvRedisAdapter`
|
|
479
|
+
|
|
480
|
+
Adapter for synchronizing `EvState` across multiple instances using Redis Pub/Sub.
|
|
481
|
+
|
|
482
|
+
### Constructor
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
new EvRedisAdapter(options?: RedisOptions | string)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Parameters:
|
|
489
|
+
|
|
490
|
+
* `options`: `RedisOptions | string` – Configuration options for the Redis client (from `ioredis`), or a Redis connection URL.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## `EvMaxListenerError`
|
|
447
495
|
|
|
448
496
|
Represents an error thrown when the number of listeners on a given channel exceeds the allowed `maxListeners` limit (default: `5000`).
|
|
449
497
|
|
|
@@ -573,6 +621,7 @@ Options for initializing a reactive state with `EvState`.
|
|
|
573
621
|
- `channel`: Channel name for broadcasting
|
|
574
622
|
- `manager`: Instance of `EvStreamManager`
|
|
575
623
|
- `key` *(optional)*: Key for wrapping state in the broadcast (default: `'value'`)
|
|
624
|
+
- `adapter` *(optional)*: Instance of `EvStateAdapter` (e.g., `EvRedisAdapter`) for distributed synchronization.
|
|
576
625
|
|
|
577
626
|
---
|
|
578
627
|
|