evstream 1.0.2 → 1.0.4
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/.me/dump.ts +102 -0
- package/dist/adapters/pub-sub.d.ts +40 -0
- package/dist/adapters/pub-sub.js +91 -0
- package/dist/adapters/redis.d.ts +46 -1
- package/dist/adapters/redis.js +72 -13
- package/dist/extensions/state-manager.d.ts +95 -0
- package/dist/extensions/state-manager.js +155 -0
- package/dist/manager.d.ts +22 -12
- package/dist/manager.js +90 -49
- package/dist/types.d.ts +2 -0
- package/package.json +5 -1
- package/readme.md +844 -674
- package/src/adapters/pub-sub.ts +88 -0
- package/src/adapters/redis.ts +120 -53
- package/src/extensions/state-manager.ts +186 -0
- package/src/manager.ts +209 -156
- package/src/types.ts +28 -25
package/.me/dump.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { EvState, EvStreamManager } from 'evstream'
|
|
2
|
+
import { EvRedisAdapter } from 'evstream/adapter/redis'
|
|
3
|
+
import type Redis from 'ioredis'
|
|
4
|
+
|
|
5
|
+
type SharedStateMessage =
|
|
6
|
+
| { action: 'create'; key: string; initialValue: any }
|
|
7
|
+
| { action: 'remove'; key: string }
|
|
8
|
+
|
|
9
|
+
export class EvCustomSharedState {
|
|
10
|
+
private states = new Map<string, EvState>()
|
|
11
|
+
private redis: Redis
|
|
12
|
+
private pub: Redis
|
|
13
|
+
private manager: EvStreamManager
|
|
14
|
+
private adapter: EvRedisAdapter
|
|
15
|
+
private controlChannel = 'ev:shared-state'
|
|
16
|
+
|
|
17
|
+
constructor(options: {
|
|
18
|
+
redis: Redis
|
|
19
|
+
publisher?: Redis
|
|
20
|
+
manager: EvStreamManager
|
|
21
|
+
adapter: EvRedisAdapter
|
|
22
|
+
}) {
|
|
23
|
+
this.redis = options.redis
|
|
24
|
+
this.pub = options.publisher ?? options.redis
|
|
25
|
+
this.manager = options.manager
|
|
26
|
+
this.adapter = options.adapter
|
|
27
|
+
|
|
28
|
+
this.init()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async init() {
|
|
32
|
+
await this.redis.subscribe(this.controlChannel)
|
|
33
|
+
|
|
34
|
+
this.redis.on('message', (_, raw) => {
|
|
35
|
+
try {
|
|
36
|
+
const msg = JSON.parse(raw) as SharedStateMessage
|
|
37
|
+
this.handleMessage(msg)
|
|
38
|
+
} catch {
|
|
39
|
+
/* ignore malformed messages */
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private handleMessage(msg: SharedStateMessage) {
|
|
45
|
+
if (msg.action === 'create') {
|
|
46
|
+
this.createLocalState(msg.key, msg.initialValue)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (msg.action === 'remove') {
|
|
50
|
+
this.removeLocalState(msg.key)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private createLocalState(key: string, initialValue: any) {
|
|
55
|
+
if (this.states.has(key)) return
|
|
56
|
+
|
|
57
|
+
const state = new EvState({
|
|
58
|
+
channel: key,
|
|
59
|
+
initialValue,
|
|
60
|
+
manager: this.manager,
|
|
61
|
+
adapter: this.adapter,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
this.states.set(key, state)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private removeLocalState(key: string) {
|
|
68
|
+
const state = this.states.get(key)
|
|
69
|
+
if (!state) return
|
|
70
|
+
|
|
71
|
+
// EvState has no destroy API, so we simply stop tracking it
|
|
72
|
+
this.states.delete(key)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ================= PUBLIC API ================= */
|
|
76
|
+
|
|
77
|
+
async create(key: string, initialValue: any) {
|
|
78
|
+
await this.pub.publish(
|
|
79
|
+
this.controlChannel,
|
|
80
|
+
JSON.stringify({ action: 'create', key, initialValue })
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async remove(key: string) {
|
|
85
|
+
await this.pub.publish(
|
|
86
|
+
this.controlChannel,
|
|
87
|
+
JSON.stringify({ action: 'remove', key })
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get(key: string): EvState | undefined {
|
|
92
|
+
return this.states.get(key)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
has(key: string): boolean {
|
|
96
|
+
return this.states.has(key)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
keys(): string[] {
|
|
100
|
+
return [...this.states.keys()]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { RedisOptions } from 'ioredis';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for EvRedisPubSub
|
|
4
|
+
*/
|
|
5
|
+
interface EvRedisPubSubOptions<T> {
|
|
6
|
+
/** Redis Pub/Sub channel name */
|
|
7
|
+
subject: string;
|
|
8
|
+
/** Redis connection options */
|
|
9
|
+
options: RedisOptions;
|
|
10
|
+
/** Optional initial message handler */
|
|
11
|
+
onMessage?: (message: T) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Redis-based Pub/Sub helper for cross-process communication.
|
|
15
|
+
*
|
|
16
|
+
* - Uses separate publisher and subscriber connections
|
|
17
|
+
* - Prevents self-message delivery using instance UID
|
|
18
|
+
* - Typed message payload via generics
|
|
19
|
+
*/
|
|
20
|
+
export declare class EvRedisPubSub<T = unknown> {
|
|
21
|
+
#private;
|
|
22
|
+
constructor({ options, subject, onMessage }: EvRedisPubSubOptions<T>);
|
|
23
|
+
/**
|
|
24
|
+
* Initializes Redis subscriptions and listeners.
|
|
25
|
+
*/
|
|
26
|
+
private init;
|
|
27
|
+
/**
|
|
28
|
+
* Publishes a message to the Redis channel.
|
|
29
|
+
*/
|
|
30
|
+
send(msg: T): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Registers or replaces the message handler.
|
|
33
|
+
*/
|
|
34
|
+
onMessage(callback: (msg: T) => void): void;
|
|
35
|
+
/**
|
|
36
|
+
* Gracefully closes Redis connections.
|
|
37
|
+
*/
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
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 _EvRedisPubSub_subject, _EvRedisPubSub_pub, _EvRedisPubSub_sub, _EvRedisPubSub_instanceId, _EvRedisPubSub_onMessage;
|
|
22
|
+
import Redis from 'ioredis';
|
|
23
|
+
import { uid } from '../utils.js';
|
|
24
|
+
/**
|
|
25
|
+
* Redis-based Pub/Sub helper for cross-process communication.
|
|
26
|
+
*
|
|
27
|
+
* - Uses separate publisher and subscriber connections
|
|
28
|
+
* - Prevents self-message delivery using instance UID
|
|
29
|
+
* - Typed message payload via generics
|
|
30
|
+
*/
|
|
31
|
+
export class EvRedisPubSub {
|
|
32
|
+
constructor({ options, subject, onMessage }) {
|
|
33
|
+
_EvRedisPubSub_subject.set(this, void 0);
|
|
34
|
+
_EvRedisPubSub_pub.set(this, void 0);
|
|
35
|
+
_EvRedisPubSub_sub.set(this, void 0);
|
|
36
|
+
_EvRedisPubSub_instanceId.set(this, void 0);
|
|
37
|
+
_EvRedisPubSub_onMessage.set(this, void 0);
|
|
38
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_pub, new Redis(options), "f");
|
|
39
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_sub, new Redis(options), "f");
|
|
40
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_subject, subject, "f");
|
|
41
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_onMessage, onMessage, "f");
|
|
42
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_instanceId, uid({ prefix: subject, counter: Math.random() }), "f");
|
|
43
|
+
this.init();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Initializes Redis subscriptions and listeners.
|
|
47
|
+
*/
|
|
48
|
+
init() {
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
__classPrivateFieldGet(this, _EvRedisPubSub_pub, "f").on('error', () => { });
|
|
51
|
+
__classPrivateFieldGet(this, _EvRedisPubSub_sub, "f").on('error', () => { });
|
|
52
|
+
yield __classPrivateFieldGet(this, _EvRedisPubSub_sub, "f").subscribe(__classPrivateFieldGet(this, _EvRedisPubSub_subject, "f"));
|
|
53
|
+
__classPrivateFieldGet(this, _EvRedisPubSub_sub, "f").on('message', (_, raw) => {
|
|
54
|
+
var _a;
|
|
55
|
+
try {
|
|
56
|
+
const data = JSON.parse(raw);
|
|
57
|
+
// Ignore messages from the same instance
|
|
58
|
+
if ((data === null || data === void 0 ? void 0 : data.uid) !== __classPrivateFieldGet(this, _EvRedisPubSub_instanceId, "f")) {
|
|
59
|
+
(_a = __classPrivateFieldGet(this, _EvRedisPubSub_onMessage, "f")) === null || _a === void 0 ? void 0 : _a.call(this, data.msg);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (_b) {
|
|
63
|
+
// Ignore malformed payloads
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Publishes a message to the Redis channel.
|
|
70
|
+
*/
|
|
71
|
+
send(msg) {
|
|
72
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
yield __classPrivateFieldGet(this, _EvRedisPubSub_pub, "f").publish(__classPrivateFieldGet(this, _EvRedisPubSub_subject, "f"), JSON.stringify({ uid: __classPrivateFieldGet(this, _EvRedisPubSub_instanceId, "f"), msg }));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Registers or replaces the message handler.
|
|
78
|
+
*/
|
|
79
|
+
onMessage(callback) {
|
|
80
|
+
__classPrivateFieldSet(this, _EvRedisPubSub_onMessage, callback, "f");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Gracefully closes Redis connections.
|
|
84
|
+
*/
|
|
85
|
+
close() {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
yield Promise.all([__classPrivateFieldGet(this, _EvRedisPubSub_pub, "f").quit(), __classPrivateFieldGet(this, _EvRedisPubSub_sub, "f").quit()]);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
_EvRedisPubSub_subject = new WeakMap(), _EvRedisPubSub_pub = new WeakMap(), _EvRedisPubSub_sub = new WeakMap(), _EvRedisPubSub_instanceId = new WeakMap(), _EvRedisPubSub_onMessage = new WeakMap();
|
package/dist/adapters/redis.d.ts
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
1
|
import { RedisOptions } from 'ioredis';
|
|
2
|
+
import { EvRedisPubSub } from './pub-sub.js';
|
|
2
3
|
import { EvStateAdapter } from '../types.js';
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Redis-based implementation of {@link EvStateAdapter}.
|
|
6
|
+
*
|
|
7
|
+
* This adapter enables distributed state updates using Redis Pub/Sub.
|
|
8
|
+
* It supports:
|
|
9
|
+
* - Channel-based subscriptions
|
|
10
|
+
* - Multiple listeners per channel
|
|
11
|
+
* - Self-message filtering via instance ID
|
|
12
|
+
*
|
|
13
|
+
* Designed to be used by EvState / EvStateManager for
|
|
14
|
+
* cross-process state synchronization.
|
|
15
|
+
*/
|
|
16
|
+
declare class EvRedisAdapter implements EvStateAdapter {
|
|
4
17
|
#private;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new Redis state adapter.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Optional Redis connection options
|
|
22
|
+
*/
|
|
5
23
|
constructor(options?: RedisOptions);
|
|
24
|
+
/**
|
|
25
|
+
* Publishes a message to a Redis channel.
|
|
26
|
+
*
|
|
27
|
+
* The payload is wrapped with the instance ID to
|
|
28
|
+
* prevent self-delivery.
|
|
29
|
+
*
|
|
30
|
+
* @param channel - Redis channel name
|
|
31
|
+
* @param message - Message payload
|
|
32
|
+
*/
|
|
6
33
|
publish(channel: string, message: any): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Subscribes to a Redis channel.
|
|
36
|
+
*
|
|
37
|
+
* Multiple listeners can be registered per channel.
|
|
38
|
+
* The Redis subscription is created only once per channel.
|
|
39
|
+
*
|
|
40
|
+
* @param channel - Redis channel name
|
|
41
|
+
* @param onMessage - Callback invoked on incoming messages
|
|
42
|
+
*/
|
|
7
43
|
subscribe(channel: string, onMessage: (message: any) => void): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Unsubscribes from a Redis channel and removes all listeners.
|
|
46
|
+
*
|
|
47
|
+
* @param channel - Redis channel name
|
|
48
|
+
*/
|
|
8
49
|
unsubscribe(channel: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Gracefully closes Redis connections.
|
|
52
|
+
*/
|
|
9
53
|
quit(): void;
|
|
10
54
|
}
|
|
55
|
+
export { EvRedisPubSub, EvRedisAdapter };
|
package/dist/adapters/redis.js
CHANGED
|
@@ -18,35 +18,85 @@ 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 _EvRedisAdapter_pub, _EvRedisAdapter_sub, _EvRedisAdapter_listeners;
|
|
21
|
+
var _EvRedisAdapter_pub, _EvRedisAdapter_sub, _EvRedisAdapter_listeners, _EvRedisAdapter_instanceId;
|
|
22
22
|
import Redis from 'ioredis';
|
|
23
|
-
|
|
23
|
+
import { EvRedisPubSub } from './pub-sub.js';
|
|
24
|
+
import { uid } from '../utils.js';
|
|
25
|
+
/**
|
|
26
|
+
* Redis-based implementation of {@link EvStateAdapter}.
|
|
27
|
+
*
|
|
28
|
+
* This adapter enables distributed state updates using Redis Pub/Sub.
|
|
29
|
+
* It supports:
|
|
30
|
+
* - Channel-based subscriptions
|
|
31
|
+
* - Multiple listeners per channel
|
|
32
|
+
* - Self-message filtering via instance ID
|
|
33
|
+
*
|
|
34
|
+
* Designed to be used by EvState / EvStateManager for
|
|
35
|
+
* cross-process state synchronization.
|
|
36
|
+
*/
|
|
37
|
+
class EvRedisAdapter {
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new Redis state adapter.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Optional Redis connection options
|
|
42
|
+
*/
|
|
24
43
|
constructor(options) {
|
|
44
|
+
/** Publisher Redis client */
|
|
25
45
|
_EvRedisAdapter_pub.set(this, void 0);
|
|
46
|
+
/** Subscriber Redis client */
|
|
26
47
|
_EvRedisAdapter_sub.set(this, void 0);
|
|
48
|
+
/**
|
|
49
|
+
* Channel → listeners mapping.
|
|
50
|
+
* Each channel may have multiple local handlers.
|
|
51
|
+
*/
|
|
27
52
|
_EvRedisAdapter_listeners.set(this, void 0);
|
|
53
|
+
/** Unique identifier for this adapter instance */
|
|
54
|
+
_EvRedisAdapter_instanceId.set(this, void 0);
|
|
28
55
|
__classPrivateFieldSet(this, _EvRedisAdapter_pub, new Redis(options), "f");
|
|
29
56
|
__classPrivateFieldSet(this, _EvRedisAdapter_sub, new Redis(options), "f");
|
|
30
57
|
__classPrivateFieldSet(this, _EvRedisAdapter_listeners, new Map(), "f");
|
|
58
|
+
__classPrivateFieldSet(this, _EvRedisAdapter_instanceId, uid({ counter: Math.ceil(Math.random() * 100) }), "f");
|
|
31
59
|
__classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").on('message', (channel, message) => {
|
|
32
60
|
const handlers = __classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").get(channel);
|
|
33
|
-
if (handlers)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
catch (_a) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
handlers.forEach((handler) => handler(parsed));
|
|
61
|
+
if (!handlers)
|
|
62
|
+
return;
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(message);
|
|
42
66
|
}
|
|
67
|
+
catch (_a) {
|
|
68
|
+
// Ignore malformed payloads
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Ignore messages published by this instance
|
|
72
|
+
if ((parsed === null || parsed === void 0 ? void 0 : parsed.id) === __classPrivateFieldGet(this, _EvRedisAdapter_instanceId, "f"))
|
|
73
|
+
return;
|
|
74
|
+
handlers.forEach((handler) => handler(parsed === null || parsed === void 0 ? void 0 : parsed.message));
|
|
43
75
|
});
|
|
44
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Publishes a message to a Redis channel.
|
|
79
|
+
*
|
|
80
|
+
* The payload is wrapped with the instance ID to
|
|
81
|
+
* prevent self-delivery.
|
|
82
|
+
*
|
|
83
|
+
* @param channel - Redis channel name
|
|
84
|
+
* @param message - Message payload
|
|
85
|
+
*/
|
|
45
86
|
publish(channel, message) {
|
|
46
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
-
yield __classPrivateFieldGet(this, _EvRedisAdapter_pub, "f").publish(channel, JSON.stringify(message));
|
|
88
|
+
yield __classPrivateFieldGet(this, _EvRedisAdapter_pub, "f").publish(channel, JSON.stringify({ id: __classPrivateFieldGet(this, _EvRedisAdapter_instanceId, "f"), message }));
|
|
48
89
|
});
|
|
49
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Subscribes to a Redis channel.
|
|
93
|
+
*
|
|
94
|
+
* Multiple listeners can be registered per channel.
|
|
95
|
+
* The Redis subscription is created only once per channel.
|
|
96
|
+
*
|
|
97
|
+
* @param channel - Redis channel name
|
|
98
|
+
* @param onMessage - Callback invoked on incoming messages
|
|
99
|
+
*/
|
|
50
100
|
subscribe(channel, onMessage) {
|
|
51
101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
52
102
|
if (!__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").has(channel)) {
|
|
@@ -56,15 +106,24 @@ export class EvRedisAdapter {
|
|
|
56
106
|
__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").get(channel).add(onMessage);
|
|
57
107
|
});
|
|
58
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Unsubscribes from a Redis channel and removes all listeners.
|
|
111
|
+
*
|
|
112
|
+
* @param channel - Redis channel name
|
|
113
|
+
*/
|
|
59
114
|
unsubscribe(channel) {
|
|
60
115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
61
116
|
yield __classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").unsubscribe(channel);
|
|
62
117
|
__classPrivateFieldGet(this, _EvRedisAdapter_listeners, "f").delete(channel);
|
|
63
118
|
});
|
|
64
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Gracefully closes Redis connections.
|
|
122
|
+
*/
|
|
65
123
|
quit() {
|
|
66
124
|
__classPrivateFieldGet(this, _EvRedisAdapter_pub, "f").quit();
|
|
67
125
|
__classPrivateFieldGet(this, _EvRedisAdapter_sub, "f").quit();
|
|
68
126
|
}
|
|
69
127
|
}
|
|
70
|
-
_EvRedisAdapter_pub = new WeakMap(), _EvRedisAdapter_sub = new WeakMap(), _EvRedisAdapter_listeners = new WeakMap();
|
|
128
|
+
_EvRedisAdapter_pub = new WeakMap(), _EvRedisAdapter_sub = new WeakMap(), _EvRedisAdapter_listeners = new WeakMap(), _EvRedisAdapter_instanceId = new WeakMap();
|
|
129
|
+
export { EvRedisPubSub, EvRedisAdapter };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { EvStreamManager } from '../manager.js';
|
|
2
|
+
import type { EvRedisAdapter } from '../adapters/redis.js';
|
|
3
|
+
import type { EvRedisPubSub } from '../adapters/pub-sub.js';
|
|
4
|
+
import { EvState } from '../state.js';
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating an {@link EvStateManager}.
|
|
7
|
+
*/
|
|
8
|
+
interface EvStateManagerOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Stream manager responsible for managing client connections
|
|
11
|
+
* and broadcasting state updates.
|
|
12
|
+
*/
|
|
13
|
+
manager: EvStreamManager;
|
|
14
|
+
/**
|
|
15
|
+
* Optional distributed state adapter (e.g. Redis).
|
|
16
|
+
* Enables cross-process state propagation.
|
|
17
|
+
*/
|
|
18
|
+
adapter?: EvRedisAdapter;
|
|
19
|
+
/**
|
|
20
|
+
* Optional Pub/Sub instance used to synchronize
|
|
21
|
+
* state creation and removal across instances.
|
|
22
|
+
*/
|
|
23
|
+
pubsub?: EvRedisPubSub;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Manages a collection of named {@link EvState} instances.
|
|
27
|
+
*
|
|
28
|
+
* Responsibilities:
|
|
29
|
+
* - Create and cache state objects locally
|
|
30
|
+
* - Synchronize state lifecycle (create/remove) across processes
|
|
31
|
+
* - Bridge EvState with stream manager and adapters
|
|
32
|
+
*
|
|
33
|
+
* Internally, all state keys are converted to strings to remain
|
|
34
|
+
* Redis-safe and transport-friendly.
|
|
35
|
+
*
|
|
36
|
+
* @typeParam S - Mapping of state keys to their value types
|
|
37
|
+
*/
|
|
38
|
+
export declare class EvStateManager<S extends Record<string, any>> {
|
|
39
|
+
#private;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new state manager.
|
|
42
|
+
*
|
|
43
|
+
* @param options - Initialization options
|
|
44
|
+
*/
|
|
45
|
+
constructor({ manager, adapter, pubsub }: EvStateManagerOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Creates a state locally without emitting Pub/Sub events.
|
|
48
|
+
*
|
|
49
|
+
* @param channel - State channel name
|
|
50
|
+
* @param initialValue - Initial state value
|
|
51
|
+
*/
|
|
52
|
+
private createLocalState;
|
|
53
|
+
/**
|
|
54
|
+
* Removes a state locally without emitting Pub/Sub events.
|
|
55
|
+
*
|
|
56
|
+
* @param channel - State channel name
|
|
57
|
+
*/
|
|
58
|
+
private removeLocalState;
|
|
59
|
+
/**
|
|
60
|
+
* Creates or returns an existing state.
|
|
61
|
+
*
|
|
62
|
+
* If Pub/Sub is enabled, the creation is broadcast
|
|
63
|
+
* to other instances.
|
|
64
|
+
*
|
|
65
|
+
* @param key - State key
|
|
66
|
+
* @param initialValue - Initial state value
|
|
67
|
+
*/
|
|
68
|
+
createState<K extends keyof S>(key: K, initialValue: S[K]): EvState<S[K]>;
|
|
69
|
+
/**
|
|
70
|
+
* Retrieves an existing state.
|
|
71
|
+
*
|
|
72
|
+
* @param key - State key
|
|
73
|
+
*/
|
|
74
|
+
getState<K extends keyof S>(key: K): EvState<S[K]> | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Checks whether a state exists.
|
|
77
|
+
*
|
|
78
|
+
* @param key - State key
|
|
79
|
+
*/
|
|
80
|
+
hasState<K extends keyof S>(key: K): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Removes a state locally and propagates the removal
|
|
83
|
+
* to other instances via Pub/Sub.
|
|
84
|
+
*
|
|
85
|
+
* @param key - State key
|
|
86
|
+
*/
|
|
87
|
+
removeState<K extends keyof S>(key: K): void;
|
|
88
|
+
/**
|
|
89
|
+
* Handles incoming Pub/Sub lifecycle events.
|
|
90
|
+
*
|
|
91
|
+
* @param msg - Pub/Sub message payload
|
|
92
|
+
*/
|
|
93
|
+
private pubSubCallback;
|
|
94
|
+
}
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
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");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
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
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _EvStateManager_states, _EvStateManager_manager, _EvStateManager_adapter, _EvStateManager_pubsub;
|
|
13
|
+
import { EvState } from '../state.js';
|
|
14
|
+
/**
|
|
15
|
+
* Manages a collection of named {@link EvState} instances.
|
|
16
|
+
*
|
|
17
|
+
* Responsibilities:
|
|
18
|
+
* - Create and cache state objects locally
|
|
19
|
+
* - Synchronize state lifecycle (create/remove) across processes
|
|
20
|
+
* - Bridge EvState with stream manager and adapters
|
|
21
|
+
*
|
|
22
|
+
* Internally, all state keys are converted to strings to remain
|
|
23
|
+
* Redis-safe and transport-friendly.
|
|
24
|
+
*
|
|
25
|
+
* @typeParam S - Mapping of state keys to their value types
|
|
26
|
+
*/
|
|
27
|
+
export class EvStateManager {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new state manager.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Initialization options
|
|
32
|
+
*/
|
|
33
|
+
constructor({ manager, adapter, pubsub }) {
|
|
34
|
+
/**
|
|
35
|
+
* Internal state registry.
|
|
36
|
+
* Keyed by string channel name.
|
|
37
|
+
*/
|
|
38
|
+
_EvStateManager_states.set(this, new Map()
|
|
39
|
+
/** Stream manager used by all states */
|
|
40
|
+
);
|
|
41
|
+
/** Stream manager used by all states */
|
|
42
|
+
_EvStateManager_manager.set(this, void 0);
|
|
43
|
+
/** Optional distributed adapter */
|
|
44
|
+
_EvStateManager_adapter.set(this, void 0);
|
|
45
|
+
/** Optional Pub/Sub synchronizer */
|
|
46
|
+
_EvStateManager_pubsub.set(this, void 0);
|
|
47
|
+
__classPrivateFieldSet(this, _EvStateManager_manager, manager, "f");
|
|
48
|
+
__classPrivateFieldSet(this, _EvStateManager_adapter, adapter, "f");
|
|
49
|
+
__classPrivateFieldSet(this, _EvStateManager_pubsub, pubsub, "f");
|
|
50
|
+
this.pubSubCallback = this.pubSubCallback.bind(this);
|
|
51
|
+
if (__classPrivateFieldGet(this, _EvStateManager_pubsub, "f")) {
|
|
52
|
+
__classPrivateFieldGet(this, _EvStateManager_pubsub, "f").onMessage(this.pubSubCallback);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates a state locally without emitting Pub/Sub events.
|
|
57
|
+
*
|
|
58
|
+
* @param channel - State channel name
|
|
59
|
+
* @param initialValue - Initial state value
|
|
60
|
+
*/
|
|
61
|
+
createLocalState(channel, initialValue) {
|
|
62
|
+
const state = new EvState({
|
|
63
|
+
channel,
|
|
64
|
+
initialValue,
|
|
65
|
+
manager: __classPrivateFieldGet(this, _EvStateManager_manager, "f"),
|
|
66
|
+
adapter: __classPrivateFieldGet(this, _EvStateManager_adapter, "f"),
|
|
67
|
+
});
|
|
68
|
+
__classPrivateFieldGet(this, _EvStateManager_states, "f").set(channel, state);
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Removes a state locally without emitting Pub/Sub events.
|
|
73
|
+
*
|
|
74
|
+
* @param channel - State channel name
|
|
75
|
+
*/
|
|
76
|
+
removeLocalState(channel) {
|
|
77
|
+
__classPrivateFieldGet(this, _EvStateManager_states, "f").delete(channel);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Creates or returns an existing state.
|
|
81
|
+
*
|
|
82
|
+
* If Pub/Sub is enabled, the creation is broadcast
|
|
83
|
+
* to other instances.
|
|
84
|
+
*
|
|
85
|
+
* @param key - State key
|
|
86
|
+
* @param initialValue - Initial state value
|
|
87
|
+
*/
|
|
88
|
+
createState(key, initialValue) {
|
|
89
|
+
var _a;
|
|
90
|
+
const channel = String(key);
|
|
91
|
+
if (__classPrivateFieldGet(this, _EvStateManager_states, "f").has(channel)) {
|
|
92
|
+
return __classPrivateFieldGet(this, _EvStateManager_states, "f").get(channel);
|
|
93
|
+
}
|
|
94
|
+
const state = this.createLocalState(channel, initialValue);
|
|
95
|
+
(_a = __classPrivateFieldGet(this, _EvStateManager_pubsub, "f")) === null || _a === void 0 ? void 0 : _a.send({
|
|
96
|
+
type: 'create',
|
|
97
|
+
channel,
|
|
98
|
+
initialValue,
|
|
99
|
+
});
|
|
100
|
+
return state;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Retrieves an existing state.
|
|
104
|
+
*
|
|
105
|
+
* @param key - State key
|
|
106
|
+
*/
|
|
107
|
+
getState(key) {
|
|
108
|
+
return __classPrivateFieldGet(this, _EvStateManager_states, "f").get(String(key));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Checks whether a state exists.
|
|
112
|
+
*
|
|
113
|
+
* @param key - State key
|
|
114
|
+
*/
|
|
115
|
+
hasState(key) {
|
|
116
|
+
return __classPrivateFieldGet(this, _EvStateManager_states, "f").has(String(key));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Removes a state locally and propagates the removal
|
|
120
|
+
* to other instances via Pub/Sub.
|
|
121
|
+
*
|
|
122
|
+
* @param key - State key
|
|
123
|
+
*/
|
|
124
|
+
removeState(key) {
|
|
125
|
+
var _a;
|
|
126
|
+
const channel = String(key);
|
|
127
|
+
this.removeLocalState(channel);
|
|
128
|
+
(_a = __classPrivateFieldGet(this, _EvStateManager_pubsub, "f")) === null || _a === void 0 ? void 0 : _a.send({
|
|
129
|
+
type: 'remove',
|
|
130
|
+
channel,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Handles incoming Pub/Sub lifecycle events.
|
|
135
|
+
*
|
|
136
|
+
* @param msg - Pub/Sub message payload
|
|
137
|
+
*/
|
|
138
|
+
pubSubCallback(msg) {
|
|
139
|
+
if (!msg || typeof msg.channel !== 'string')
|
|
140
|
+
return;
|
|
141
|
+
switch (msg.type) {
|
|
142
|
+
case 'create': {
|
|
143
|
+
if (!__classPrivateFieldGet(this, _EvStateManager_states, "f").has(msg.channel)) {
|
|
144
|
+
this.createLocalState(msg.channel, msg.initialValue);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'remove': {
|
|
149
|
+
this.removeLocalState(msg.channel);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_EvStateManager_states = new WeakMap(), _EvStateManager_manager = new WeakMap(), _EvStateManager_adapter = new WeakMap(), _EvStateManager_pubsub = new WeakMap();
|