@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.
@@ -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 tghe cluster primary and the cluster worker.
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 tghe cluster primary and the cluster worker.
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 {