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.
@@ -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
- res.end();
78
+ // End response if not already ended
79
+ if (!res.writableEnded) {
80
+ res.end();
81
+ }
71
82
  };
72
- res.on('close', () => {
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
- var _a;
106
- if (!__classPrivateFieldGet(this, _EvStreamManager_listeners, "f").has(name)) {
107
- const size = (_a = __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").get(name)) === null || _a === void 0 ? void 0 : _a.size;
108
- if (size >= __classPrivateFieldGet(this, _EvStreamManager_maxListeners, "f")) {
109
- throw new EvMaxListenerError(size, name);
110
- }
111
- __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").set(name, new Set());
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
- __classPrivateFieldGet(this, _EvStreamManager_listeners, "f").get(name).add(id);
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
@@ -26,6 +26,7 @@ export declare class Evstream {
26
26
  message(msg: EvMessage): void;
27
27
  /**
28
28
  * Sends an "end" event and closes the SSE connection.
29
+ * Cleans up heartbeat interval and event listeners to prevent memory leaks.
29
30
  */
30
31
  close(): void;
31
32
  }
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
- const timeout = setInterval(() => {
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
- __classPrivateFieldGet(this, _Evstream_res, "f").on('close', () => {
51
- clearTimeout(timeout);
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.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
- "scripts": {
23
- "dev": "tsc --watch",
24
- "clean": "rimraf ./dist",
25
- "build": "rimraf ./dist && tsc --incremental false"
26
- },
27
- "engines": {
28
- "node": ">=17.8.0"
29
- },
30
- "devDependencies": {
31
- "@types/lodash": "^4.17.19",
32
- "@types/node": "^24.0.7",
33
- "@typescript-eslint/eslint-plugin": "^8.35.0",
34
- "@typescript-eslint/parser": "^8.35.0",
35
- "eslint": "^9.30.0",
36
- "eslint-config-prettier": "^10.1.5",
37
- "eslint-plugin-import": "^2.32.0",
38
- "eslint-plugin-prettier": "^5.5.1",
39
- "eslint-plugin-simple-import-sort": "^12.1.1",
40
- "prettier": "^3.6.2",
41
- "rimraf": "^6.0.1",
42
- "typescript": "^5.8.3"
43
- },
44
- "dependencies": {
45
- "lodash": "^4.17.21"
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. Sending data to a channel
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
- ### 6. Listening for channels
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
- ## `EvMaxListenerError`
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