@nsshunt/stsmessaging 1.0.78 → 1.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.IPCMessageHandler.md +624 -0
- package/README.IPCMessageHandlerPair.md +702 -0
- package/README.IPCMessageHandlerPairManager.md +1289 -0
- package/README.IPCMessageHandlerPairManagerGenNotes.md +420 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/types/ipcMessageHandler.d.ts +1 -1
- package/types/ipcMessageHandlerPair.d.ts +2 -1
- package/types/ipcMessageHandlerPair.d.ts.map +1 -1
- package/types/ipcMessageHandlerPairManager.d.ts +152 -0
- package/types/ipcMessageHandlerPairManager.d.ts.map +1 -0
- package/types/ipcMessageHandlerPairManager.test.d.ts +3 -0
- package/types/ipcMessageHandlerPairManager.test.d.ts.map +1 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
Yes — I think this is a cleaner architecture.
|
|
2
|
+
|
|
3
|
+
I created the complete new class here:
|
|
4
|
+
|
|
5
|
+
[Download `ipcMessageHandlerPairManager.ts`](sandbox:/mnt/data/ipcMessageHandlerPairManager.ts)
|
|
6
|
+
|
|
7
|
+
### File: `ipcMessageHandlerPairManager.ts`
|
|
8
|
+
|
|
9
|
+
The structure is now:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
IPCMessageHandlerPairManager
|
|
13
|
+
│
|
|
14
|
+
├── Worker 1 -> IPCMessageHandlerPair -> MessagingManager
|
|
15
|
+
├── Worker 2 -> IPCMessageHandlerPair -> MessagingManager
|
|
16
|
+
├── Worker 3 -> IPCMessageHandlerPair -> MessagingManager
|
|
17
|
+
└── Worker 4 -> IPCMessageHandlerPair -> MessagingManager
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each `IPCMessageHandlerPair` continues doing exactly what it is good at: managing **one worker connection**. The existing pair already stores/starts a single worker relationship.
|
|
21
|
+
|
|
22
|
+
The new manager takes over the things that were previously mixed into `IPCMessageHandler`: worker collection management and broadcasting.
|
|
23
|
+
|
|
24
|
+
The important methods are:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
manager.AddWorker(worker);
|
|
28
|
+
|
|
29
|
+
manager.RemoveWorker(worker.id);
|
|
30
|
+
|
|
31
|
+
manager.GetPair(worker.id);
|
|
32
|
+
|
|
33
|
+
manager.GetWorker(worker.id);
|
|
34
|
+
|
|
35
|
+
manager.on("some-event", callback);
|
|
36
|
+
|
|
37
|
+
await manager.SendMessageTo(worker.id, payload);
|
|
38
|
+
|
|
39
|
+
await manager.SendMessageToAll(payload);
|
|
40
|
+
|
|
41
|
+
await manager.emitTo(worker.id, "some-event", arg1, arg2);
|
|
42
|
+
|
|
43
|
+
await manager.emitToAll("some-event", arg1, arg2);
|
|
44
|
+
|
|
45
|
+
await manager.emitNoResponseTo(worker.id, "some-event", arg1);
|
|
46
|
+
|
|
47
|
+
await manager.emitNoResponseToAll("some-event", arg1);
|
|
48
|
+
|
|
49
|
+
manager.Stop();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
I also added:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
await manager.SendMessageToAllSettled(payload);
|
|
56
|
+
|
|
57
|
+
await manager.emitToAllSettled("some-event", arg1);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
These are useful when broadcasting because one dead/timed-out worker won't cause you to lose the results from every other worker.
|
|
61
|
+
|
|
62
|
+
For example:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const results = await pairManager.emitToAllSettled("get-status");
|
|
66
|
+
|
|
67
|
+
for (const result of results) {
|
|
68
|
+
if (result.status === "fulfilled") {
|
|
69
|
+
console.log(`Worker ${result.workerId}:`, result.response);
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`Worker ${result.workerId} failed:`, result.error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Event registrations are handled properly
|
|
77
|
+
|
|
78
|
+
This is something I specifically accounted for.
|
|
79
|
+
|
|
80
|
+
If you do:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
pairManager.on("from-worker", (arg1, callback) => {
|
|
84
|
+
callback({
|
|
85
|
+
status: "OK",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
the manager registers that event on **every existing pair**.
|
|
91
|
+
|
|
92
|
+
More importantly, it also remembers the registration. So if another worker is added later:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
pairManager.AddWorker(newWorker);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
that new pair automatically receives all the existing event registrations.
|
|
99
|
+
|
|
100
|
+
Without this, this architecture would have a subtle bug where:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
manager.on(...)
|
|
104
|
+
AddWorker(worker1)
|
|
105
|
+
AddWorker(worker2)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
would behave differently from:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
AddWorker(worker1)
|
|
112
|
+
AddWorker(worker2)
|
|
113
|
+
manager.on(...)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The manager makes both cases work.
|
|
117
|
+
|
|
118
|
+
### Example primary process
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { IPCMessageHandlerPairManager } from "./ipcMessageHandlerPairManager.js";
|
|
122
|
+
|
|
123
|
+
const pairManager = new IPCMessageHandlerPairManager({
|
|
124
|
+
logger: this.options.logger,
|
|
125
|
+
requestResponseMessageTimeout: 5000,
|
|
126
|
+
namespace: "STS_WORKERS",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
pairManager.on("worker-ready", (data, callback) => {
|
|
130
|
+
console.log("Worker ready:", data);
|
|
131
|
+
|
|
132
|
+
callback({
|
|
133
|
+
status: "OK",
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const worker1 = cluster.fork();
|
|
138
|
+
const worker2 = cluster.fork();
|
|
139
|
+
const worker3 = cluster.fork();
|
|
140
|
+
|
|
141
|
+
pairManager.AddWorker(worker1);
|
|
142
|
+
pairManager.AddWorker(worker2);
|
|
143
|
+
pairManager.AddWorker(worker3);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
You can now address one worker:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const response = await pairManager.emitTo(worker2.id, "get-status");
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
or broadcast:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const responses = await pairManager.emitToAll("get-status");
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
That distinction is much cleaner than having `IPCMessageHandler.SendMessage()` silently mean "send to everybody."
|
|
159
|
+
|
|
160
|
+
### Worker side stays simple
|
|
161
|
+
|
|
162
|
+
Inside each worker, you **do not use the manager**.
|
|
163
|
+
|
|
164
|
+
You continue using a single `IPCMessageHandlerPair`:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const ipc = new IPCMessageHandlerPair({
|
|
168
|
+
logger,
|
|
169
|
+
requestResponseMessageTimeout: 5000,
|
|
170
|
+
namespace: "STS_WORKERS",
|
|
171
|
+
role: "CLIENT",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
ipc.on("get-status", (callback) => {
|
|
175
|
+
callback({
|
|
176
|
+
pid: process.pid,
|
|
177
|
+
status: "OK",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
ipc.Start();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
So:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
PRIMARY
|
|
188
|
+
|
|
189
|
+
IPCMessageHandlerPairManager
|
|
190
|
+
│
|
|
191
|
+
├── Pair(worker 1)
|
|
192
|
+
├── Pair(worker 2)
|
|
193
|
+
└── Pair(worker 3)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
WORKER 1 WORKER 2 WORKER 3
|
|
197
|
+
|
|
198
|
+
Pair(CLIENT) Pair(CLIENT) Pair(CLIENT)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Your `MessagingManager` fits this design particularly well
|
|
202
|
+
|
|
203
|
+
There's an important reason I think this architecture fits your existing code.
|
|
204
|
+
|
|
205
|
+
Each `MessagingManager` creates its own unique message header. Responses are matched against that specific header and the manager's own inflight-message records.
|
|
206
|
+
|
|
207
|
+
But incoming requests are accepted based on the namespace prefix:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const checkName = `__STS__${this.#options.namespace}__`;
|
|
211
|
+
|
|
212
|
+
if ((msg.header as string).includes(checkName)) {
|
|
213
|
+
...
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
That means you can have:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
Pair 1 -> MessagingManager(unique ID/header)
|
|
221
|
+
Pair 2 -> MessagingManager(unique ID/header)
|
|
222
|
+
Pair 3 -> MessagingManager(unique ID/header)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
all using:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
namespace: "STS_WORKERS";
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Requests get recognised by the common namespace, while responses are correlated back to the specific `MessagingManager` that initiated them.
|
|
232
|
+
|
|
233
|
+
That is almost exactly what you want for this pair-per-worker architecture.
|
|
234
|
+
|
|
235
|
+
One other thing I deliberately included is automatic cleanup:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
worker.once("exit", ...)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
By default, when a worker exits, its pair is automatically:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
stopped
|
|
245
|
+
message listener removed
|
|
246
|
+
pair removed from manager
|
|
247
|
+
worker removed from manager
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
You can disable that with:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
autoRemoveOnWorkerExit: false;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
if your existing process manager already wants complete control over worker cleanup.
|
|
257
|
+
|
|
258
|
+
I think this new manager + `IPCMessageHandlerPair` can ultimately **replace `IPCMessageHandler` entirely**. The old class's one-to-many responsibilities now sit at the correct level, while the IPC transport itself remains one-to-one.
|
|
259
|
+
|
|
260
|
+
# what is the difference between sendmessagetoall vs emittoall
|
|
261
|
+
|
|
262
|
+
They use the **same underlying request/response mechanism**, but they operate at different abstraction levels.
|
|
263
|
+
|
|
264
|
+
### `SendMessageToAll()`
|
|
265
|
+
|
|
266
|
+
This is the lower-level API.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
await manager.SendMessageToAll({
|
|
270
|
+
command: "get-status",
|
|
271
|
+
someOtherData: 123,
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
It takes an arbitrary `JSONObject` and sends that exact payload to every pair:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
pair.SendMessage(payload);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Conceptually:
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
SendMessageToAll(payload)
|
|
285
|
+
|
|
|
286
|
+
+--> pair1.SendMessage(payload)
|
|
287
|
+
+--> pair2.SendMessage(payload)
|
|
288
|
+
+--> pair3.SendMessage(payload)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
It doesn't know anything about your `on("event", ...)` event system.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### `emitToAll()`
|
|
296
|
+
|
|
297
|
+
This is the higher-level **event API**.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
await manager.emitToAll("get-status", arg1, arg2);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Internally it converts that into:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
{
|
|
307
|
+
__eventName: "get-status",
|
|
308
|
+
args: [
|
|
309
|
+
arg1,
|
|
310
|
+
arg2,
|
|
311
|
+
],
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
and then calls:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
SendMessageToAll(...)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
So effectively:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
emitToAll = async (event: string, ...args: any[]) => {
|
|
325
|
+
return this.SendMessageToAll({
|
|
326
|
+
__eventName: event,
|
|
327
|
+
args,
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The receiving `IPCMessageHandlerPair` then sees `__eventName` and invokes the handler registered using:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
pair.on("get-status", callback);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
So the relationship is:
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
emitToAll("get-status", arg1)
|
|
344
|
+
|
|
|
345
|
+
v
|
|
346
|
+
{
|
|
347
|
+
__eventName: "get-status",
|
|
348
|
+
args: [arg1]
|
|
349
|
+
}
|
|
350
|
+
|
|
|
351
|
+
v
|
|
352
|
+
SendMessageToAll(...)
|
|
353
|
+
|
|
|
354
|
+
+----> Worker 1
|
|
355
|
+
+----> Worker 2
|
|
356
|
+
+----> Worker 3
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Practical difference
|
|
360
|
+
|
|
361
|
+
| | `SendMessageToAll()` | `emitToAll()` |
|
|
362
|
+
| ------------------------------------- | -------------------- | ---------------------- |
|
|
363
|
+
| Level | Low-level | High-level |
|
|
364
|
+
| Input | Any `JSONObject` | Event name + arguments |
|
|
365
|
+
| Uses `.on()` handlers | Not automatically | Yes |
|
|
366
|
+
| Adds `__eventName` | No | Yes |
|
|
367
|
+
| Adds `args` | No | Yes |
|
|
368
|
+
| Uses `MessagingManager.SendMessage()` | Yes | Yes |
|
|
369
|
+
| Expects responses | Yes | Yes |
|
|
370
|
+
| Broadcasts | Yes | Yes |
|
|
371
|
+
|
|
372
|
+
For your framework, I would expect you to use **`emitToAll()` most of the time**.
|
|
373
|
+
|
|
374
|
+
For example:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
manager.on("reload-config", (config, callback) => {
|
|
378
|
+
// ...
|
|
379
|
+
callback({
|
|
380
|
+
status: "OK",
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Then:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const responses = await manager.emitToAll("reload-config", config);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
That's much nicer than manually doing:
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
await manager.SendMessageToAll({
|
|
395
|
+
__eventName: "reload-config",
|
|
396
|
+
args: [config],
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
In fact, those two examples are essentially equivalent.
|
|
401
|
+
|
|
402
|
+
I'd treat them like this:
|
|
403
|
+
|
|
404
|
+
```text
|
|
405
|
+
SendMessageToAll()
|
|
406
|
+
= primitive messaging API
|
|
407
|
+
|
|
408
|
+
emitToAll()
|
|
409
|
+
= event-oriented convenience API built on top of it
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
And the same relationship applies to:
|
|
413
|
+
|
|
414
|
+
```text
|
|
415
|
+
SendMessageTo(workerId, payload)
|
|
416
|
+
vs
|
|
417
|
+
emitTo(workerId, event, ...args)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
For your public framework API, you may eventually find that `SendMessageToAll()` is mostly an internal building block while `emitToAll()` is what application code normally uses.
|
package/dist/index.cjs
CHANGED
|
@@ -403,7 +403,7 @@ var RedisMessageHandler = class extends tiny_emitter.TinyEmitter {
|
|
|
403
403
|
* IPC Message Handling.
|
|
404
404
|
*
|
|
405
405
|
* This class can be used to support messages between cluster.primary and cluster.worker instances using IPC.
|
|
406
|
-
* This class can be used for both
|
|
406
|
+
* This class can be used for both the cluster primary and the cluster worker.
|
|
407
407
|
* Note: Currently groups handling is not supported. Use the redis version for this capability.
|
|
408
408
|
*/
|
|
409
409
|
var IPCMessageHandler = class extends tiny_emitter.TinyEmitter {
|
|
@@ -575,7 +575,7 @@ var IPCMessageHandler = class extends tiny_emitter.TinyEmitter {
|
|
|
575
575
|
* IPC Message Handling.
|
|
576
576
|
*
|
|
577
577
|
* This class can be used to support messages between cluster.primary and cluster.worker instances using IPC.
|
|
578
|
-
* This class can be used for both
|
|
578
|
+
* This class can be used for both the cluster primary and the cluster worker.
|
|
579
579
|
* Note: Currently groups handling is not supported. Use the redis version for this capability.
|
|
580
580
|
*/
|
|
581
581
|
var IPCMessageHandlerPair = class extends tiny_emitter.TinyEmitter {
|