@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,1289 @@
1
+ # IPCMessageHandlerPairManager
2
+
3
+ `IPCMessageHandlerPairManager` manages a collection of `IPCMessageHandlerPair` instances on the **server / cluster primary** side.
4
+
5
+ Each worker gets its own dedicated `IPCMessageHandlerPair`.
6
+
7
+ The manager is responsible for:
8
+
9
+ - maintaining the worker collection;
10
+ - creating and removing worker pairs;
11
+ - sending messages to one worker;
12
+ - broadcasting messages to all workers;
13
+ - registering events across every pair;
14
+ - applying existing event registrations to workers added later;
15
+ - automatically cleaning up worker pairs when workers exit.
16
+
17
+ ---
18
+
19
+ # Architecture
20
+
21
+ The manager uses a **collection of one-to-one IPC pairs**.
22
+
23
+ ```text
24
+ IPCMessageHandlerPairManager
25
+
26
+ ├── Worker 1 -> IPCMessageHandlerPair -> MessagingManager
27
+ ├── Worker 2 -> IPCMessageHandlerPair -> MessagingManager
28
+ ├── Worker 3 -> IPCMessageHandlerPair -> MessagingManager
29
+ └── Worker 4 -> IPCMessageHandlerPair -> MessagingManager
30
+ ```
31
+
32
+ Each `IPCMessageHandlerPair` is responsible for exactly one primary/worker IPC relationship.
33
+
34
+ The manager owns the collection.
35
+
36
+ This separates responsibilities cleanly:
37
+
38
+ ```text
39
+ IPCMessageHandlerPairManager
40
+ -> worker collection
41
+ -> worker lifecycle
42
+ -> targeted messaging
43
+ -> broadcasting
44
+ -> event propagation
45
+
46
+ IPCMessageHandlerPair
47
+ -> one worker connection
48
+ -> IPC send/receive
49
+ -> event dispatch
50
+ -> request/response messaging
51
+
52
+ MessagingManager
53
+ -> message IDs
54
+ -> request/response correlation
55
+ -> timeouts
56
+ -> message protocol
57
+ ```
58
+
59
+ ---
60
+
61
+ # Why This Class Exists
62
+
63
+ Previously, `IPCMessageHandler` combined two separate responsibilities:
64
+
65
+ ```text
66
+ IPC connection handling
67
+ +
68
+ worker collection management
69
+ ```
70
+
71
+ `IPCMessageHandlerPairManager` separates those responsibilities.
72
+
73
+ Instead of:
74
+
75
+ ```text
76
+ IPCMessageHandler
77
+ |
78
+ +-- Worker 1
79
+ +-- Worker 2
80
+ +-- Worker 3
81
+ ```
82
+
83
+ the architecture becomes:
84
+
85
+ ```text
86
+ IPCMessageHandlerPairManager
87
+ |
88
+ +-- IPCMessageHandlerPair -> Worker 1
89
+ +-- IPCMessageHandlerPair -> Worker 2
90
+ +-- IPCMessageHandlerPair -> Worker 3
91
+ ```
92
+
93
+ This allows the pair class to remain focused on a single IPC connection while the manager handles multi-worker behavior.
94
+
95
+ ---
96
+
97
+ # File
98
+
99
+ ```text
100
+ ipcMessageHandlerPairManager.ts
101
+ ```
102
+
103
+ ---
104
+
105
+ # Constructor
106
+
107
+ ```ts
108
+ new IPCMessageHandlerPairManager(options);
109
+ ```
110
+
111
+ ## Options
112
+
113
+ ```ts
114
+ export interface IPCMessageHandlerPairManagerOptions {
115
+ logger: ISTSLogger;
116
+ requestResponseMessageTimeout: number;
117
+ namespace: string;
118
+ ignoreEvents?: string[];
119
+ autoRemoveOnWorkerExit?: boolean;
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## logger
126
+
127
+ Logger passed to every `IPCMessageHandlerPair`.
128
+
129
+ ```ts
130
+ logger: ISTSLogger;
131
+ ```
132
+
133
+ ---
134
+
135
+ ## requestResponseMessageTimeout
136
+
137
+ Request/response timeout passed to each pair.
138
+
139
+ ```ts
140
+ requestResponseMessageTimeout: 5000;
141
+ ```
142
+
143
+ Each pair creates its own `MessagingManager`, so each worker connection has its own in-flight request tracking and timeout handling.
144
+
145
+ ---
146
+
147
+ ## namespace
148
+
149
+ Messaging namespace shared by all pairs managed by this instance.
150
+
151
+ Example:
152
+
153
+ ```ts
154
+ namespace: "STS_WORKERS";
155
+ ```
156
+
157
+ All worker pairs can use the same namespace while their underlying `MessagingManager` instances maintain their own message identities.
158
+
159
+ ---
160
+
161
+ ## ignoreEvents
162
+
163
+ Optional event names whose errors should be ignored by the pair's `emit()` implementation.
164
+
165
+ ```ts
166
+ ignoreEvents: ["optional-event"];
167
+ ```
168
+
169
+ ---
170
+
171
+ ## autoRemoveOnWorkerExit
172
+
173
+ Controls whether a pair is automatically removed when its cluster worker exits.
174
+
175
+ ```ts
176
+ autoRemoveOnWorkerExit: true;
177
+ ```
178
+
179
+ This is the default.
180
+
181
+ Disable automatic cleanup with:
182
+
183
+ ```ts
184
+ autoRemoveOnWorkerExit: false;
185
+ ```
186
+
187
+ This can be useful when another higher-level process manager already owns worker lifecycle cleanup.
188
+
189
+ ---
190
+
191
+ # Internal Collections
192
+
193
+ The manager maintains three important collections.
194
+
195
+ ## Pair Collection
196
+
197
+ ```ts
198
+ Map<number, IPCMessageHandlerPair>;
199
+ ```
200
+
201
+ The key is the Node.js cluster worker ID.
202
+
203
+ Conceptually:
204
+
205
+ ```text
206
+ 1 -> IPCMessageHandlerPair
207
+ 2 -> IPCMessageHandlerPair
208
+ 3 -> IPCMessageHandlerPair
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Worker Collection
214
+
215
+ The manager also retains the actual `Worker` references.
216
+
217
+ Conceptually:
218
+
219
+ ```text
220
+ 1 -> Worker
221
+ 2 -> Worker
222
+ 3 -> Worker
223
+ ```
224
+
225
+ This allows worker lifecycle management to remain separate from the pair abstraction.
226
+
227
+ ---
228
+
229
+ ## Event Collection
230
+
231
+ Manager-level event handlers are also retained.
232
+
233
+ Conceptually:
234
+
235
+ ```text
236
+ "worker-ready" -> callback
237
+ "status" -> callback
238
+ "shutdown" -> callback
239
+ ```
240
+
241
+ This is important because workers can be added after events have already been registered.
242
+
243
+ ---
244
+
245
+ # size
246
+
247
+ Returns the number of workers currently managed.
248
+
249
+ ```ts
250
+ manager.size;
251
+ ```
252
+
253
+ Example:
254
+
255
+ ```ts
256
+ console.log(manager.size);
257
+ ```
258
+
259
+ If three workers are registered:
260
+
261
+ ```text
262
+ 3
263
+ ```
264
+
265
+ ---
266
+
267
+ # pairs
268
+
269
+ Returns a read-only view of the current worker-to-pair map.
270
+
271
+ ```ts
272
+ manager.pairs;
273
+ ```
274
+
275
+ Example:
276
+
277
+ ```ts
278
+ for (const [workerId, pair] of manager.pairs) {
279
+ console.log(workerId, pair);
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ # workerIds
286
+
287
+ Returns all worker IDs currently managed.
288
+
289
+ ```ts
290
+ manager.workerIds;
291
+ ```
292
+
293
+ Example:
294
+
295
+ ```ts
296
+ const ids = manager.workerIds;
297
+ ```
298
+
299
+ Possible result:
300
+
301
+ ```ts
302
+ [1, 2, 3];
303
+ ```
304
+
305
+ ---
306
+
307
+ # AddWorker()
308
+
309
+ Adds a cluster worker to the manager and creates its dedicated `IPCMessageHandlerPair`.
310
+
311
+ ```ts
312
+ manager.AddWorker(worker);
313
+ ```
314
+
315
+ Example:
316
+
317
+ ```ts
318
+ const worker = cluster.fork();
319
+
320
+ manager.AddWorker(worker);
321
+ ```
322
+
323
+ Internally the manager:
324
+
325
+ 1. checks whether the worker is already registered;
326
+ 2. creates a new `IPCMessageHandlerPair`;
327
+ 3. configures the pair with `role: "SERVER"`;
328
+ 4. copies all existing manager event registrations into the pair;
329
+ 5. starts the pair with the worker;
330
+ 6. stores the pair;
331
+ 7. stores the worker;
332
+ 8. optionally registers automatic worker-exit cleanup.
333
+
334
+ Conceptually:
335
+
336
+ ```text
337
+ AddWorker(worker)
338
+ |
339
+ v
340
+ Create IPCMessageHandlerPair
341
+ |
342
+ v
343
+ Apply existing events
344
+ |
345
+ v
346
+ pair.Start(worker)
347
+ |
348
+ v
349
+ Store pair + worker
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Existing Worker
355
+
356
+ If the same `Worker` instance is already registered, the existing pair is returned.
357
+
358
+ ```ts
359
+ const pair1 = manager.AddWorker(worker);
360
+ const pair2 = manager.AddWorker(worker);
361
+
362
+ pair1 === pair2;
363
+ ```
364
+
365
+ Result:
366
+
367
+ ```text
368
+ true
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Reused Worker ID
374
+
375
+ If a worker ID already exists but belongs to a different `Worker` instance, the old pair is removed before the new one is added.
376
+
377
+ This helps avoid stale worker/pair relationships.
378
+
379
+ ---
380
+
381
+ # RemoveWorker()
382
+
383
+ Stops and removes a worker's pair.
384
+
385
+ The method accepts either:
386
+
387
+ ```ts
388
+ manager.RemoveWorker(worker);
389
+ ```
390
+
391
+ or:
392
+
393
+ ```ts
394
+ manager.RemoveWorker(worker.id);
395
+ ```
396
+
397
+ It returns:
398
+
399
+ ```ts
400
+ boolean;
401
+ ```
402
+
403
+ `true` means a worker/pair was removed.
404
+
405
+ `false` means no pair existed for that worker ID.
406
+
407
+ Internally it:
408
+
409
+ 1. removes any automatic `"exit"` listener;
410
+ 2. stops the pair;
411
+ 3. removes the pair;
412
+ 4. removes the worker reference.
413
+
414
+ ---
415
+
416
+ # HasWorker()
417
+
418
+ Checks whether a worker is currently managed.
419
+
420
+ ```ts
421
+ manager.HasWorker(workerId);
422
+ ```
423
+
424
+ Example:
425
+
426
+ ```ts
427
+ if (manager.HasWorker(worker.id)) {
428
+ console.log("Worker is registered");
429
+ }
430
+ ```
431
+
432
+ ---
433
+
434
+ # GetPair()
435
+
436
+ Returns the `IPCMessageHandlerPair` associated with a worker ID.
437
+
438
+ ```ts
439
+ const pair = manager.GetPair(workerId);
440
+ ```
441
+
442
+ Return type:
443
+
444
+ ```ts
445
+ IPCMessageHandlerPair | undefined;
446
+ ```
447
+
448
+ This is useful when direct access to a pair is required.
449
+
450
+ ---
451
+
452
+ # GetWorker()
453
+
454
+ Returns the cluster `Worker` associated with a worker ID.
455
+
456
+ ```ts
457
+ const worker = manager.GetWorker(workerId);
458
+ ```
459
+
460
+ Return type:
461
+
462
+ ```ts
463
+ Worker | undefined;
464
+ ```
465
+
466
+ ---
467
+
468
+ # Event Registration
469
+
470
+ The manager exposes:
471
+
472
+ ```ts
473
+ on();
474
+ off();
475
+ ```
476
+
477
+ These behave differently from calling `on()` directly on one pair because the registration applies across the entire pair collection.
478
+
479
+ ---
480
+
481
+ # on()
482
+
483
+ Registers an incoming IPC event across all workers.
484
+
485
+ ```ts
486
+ manager.on(event, callback);
487
+ ```
488
+
489
+ Example:
490
+
491
+ ```ts
492
+ manager.on("worker-ready", (data, callback) => {
493
+ console.log("Worker ready:", data);
494
+
495
+ callback({
496
+ status: "OK",
497
+ });
498
+ });
499
+ ```
500
+
501
+ The handler is applied to:
502
+
503
+ ```text
504
+ Pair 1
505
+ Pair 2
506
+ Pair 3
507
+ ...
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Event Propagation to Future Workers
513
+
514
+ The manager also remembers event registrations.
515
+
516
+ For example:
517
+
518
+ ```ts
519
+ manager.on("get-status", callback);
520
+
521
+ manager.AddWorker(worker1);
522
+ manager.AddWorker(worker2);
523
+ ```
524
+
525
+ Both workers receive the event registration even though they were added after `on()` was called.
526
+
527
+ The reverse order also works:
528
+
529
+ ```ts
530
+ manager.AddWorker(worker1);
531
+ manager.AddWorker(worker2);
532
+
533
+ manager.on("get-status", callback);
534
+ ```
535
+
536
+ The event is immediately registered on both existing pairs.
537
+
538
+ Therefore these two sequences are equivalent:
539
+
540
+ ```text
541
+ Register event
542
+ Add workers
543
+ ```
544
+
545
+ and:
546
+
547
+ ```text
548
+ Add workers
549
+ Register event
550
+ ```
551
+
552
+ This is an important responsibility of the manager because every `IPCMessageHandlerPair` owns its own event map.
553
+
554
+ ---
555
+
556
+ # off()
557
+
558
+ Removes an event from all current pairs.
559
+
560
+ ```ts
561
+ manager.off("get-status");
562
+ ```
563
+
564
+ The event is also removed from the manager's retained event collection, so workers added later will not receive it.
565
+
566
+ ---
567
+
568
+ # Targeted Messaging
569
+
570
+ One of the main benefits of the pair-based architecture is that the manager can target an individual worker directly.
571
+
572
+ ---
573
+
574
+ # SendMessageTo()
575
+
576
+ Sends a raw request/response payload to one worker.
577
+
578
+ ```ts
579
+ const response = await manager.SendMessageTo(workerId, payload);
580
+ ```
581
+
582
+ Example:
583
+
584
+ ```ts
585
+ const response = await manager.SendMessageTo(worker.id, {
586
+ command: "status",
587
+ });
588
+ ```
589
+
590
+ Conceptually:
591
+
592
+ ```text
593
+ Manager
594
+ |
595
+ +------> Pair 2 ------> Worker 2
596
+ ```
597
+
598
+ Only the selected worker receives the message.
599
+
600
+ ---
601
+
602
+ # emitTo()
603
+
604
+ Sends an event to one worker and returns its response.
605
+
606
+ ```ts
607
+ const response = await manager.emitTo(workerId, event, ...args);
608
+ ```
609
+
610
+ Example:
611
+
612
+ ```ts
613
+ const response = await manager.emitTo(worker.id, "get-status");
614
+ ```
615
+
616
+ With arguments:
617
+
618
+ ```ts
619
+ const response = await manager.emitTo(
620
+ worker.id,
621
+ "process-resource",
622
+ resourceId
623
+ );
624
+ ```
625
+
626
+ Conceptually:
627
+
628
+ ```text
629
+ Primary
630
+ |
631
+ v
632
+ Manager
633
+ |
634
+ v
635
+ Pair(workerId)
636
+ |
637
+ v
638
+ Worker
639
+ ```
640
+
641
+ ---
642
+
643
+ # emitNoResponseTo()
644
+
645
+ Sends a fire-and-forget event to one worker.
646
+
647
+ ```ts
648
+ await manager.emitNoResponseTo(workerId, event, ...args);
649
+ ```
650
+
651
+ Example:
652
+
653
+ ```ts
654
+ await manager.emitNoResponseTo(worker.id, "refresh-config");
655
+ ```
656
+
657
+ No response is expected.
658
+
659
+ ---
660
+
661
+ # Broadcasting
662
+
663
+ The manager provides explicit broadcast operations.
664
+
665
+ This is intentionally different from `IPCMessageHandler`, where server-side `SendMessage()` implicitly meant "send to every worker."
666
+
667
+ With the pair manager the caller chooses explicitly between:
668
+
669
+ ```text
670
+ Send to one worker
671
+ ```
672
+
673
+ and:
674
+
675
+ ```text
676
+ Send to all workers
677
+ ```
678
+
679
+ ---
680
+
681
+ # SendMessageToAll()
682
+
683
+ Sends a raw payload to all workers.
684
+
685
+ ```ts
686
+ const responses = await manager.SendMessageToAll(payload);
687
+ ```
688
+
689
+ Return type:
690
+
691
+ ```ts
692
+ IPCMessageHandlerPairManagerResponse[]
693
+ ```
694
+
695
+ Each result contains:
696
+
697
+ ```ts
698
+ {
699
+ (workerId, response);
700
+ }
701
+ ```
702
+
703
+ Example:
704
+
705
+ ```ts
706
+ const responses = await manager.SendMessageToAll({
707
+ command: "status",
708
+ });
709
+
710
+ for (const result of responses) {
711
+ console.log(result.workerId, result.response);
712
+ }
713
+ ```
714
+
715
+ ---
716
+
717
+ ## Promise.all() Behavior
718
+
719
+ `SendMessageToAll()` uses `Promise.all()` semantics.
720
+
721
+ Therefore if one worker rejects or times out:
722
+
723
+ ```text
724
+ Worker 1 -> success
725
+ Worker 2 -> timeout
726
+ Worker 3 -> success
727
+ ```
728
+
729
+ the entire broadcast Promise rejects.
730
+
731
+ Use `SendMessageToAllSettled()` when every worker result is required regardless of individual failures.
732
+
733
+ ---
734
+
735
+ # SendMessageToAllSettled()
736
+
737
+ Broadcasts a raw payload without allowing one failed worker to reject the complete operation.
738
+
739
+ ```ts
740
+ const responses = await manager.SendMessageToAllSettled(payload);
741
+ ```
742
+
743
+ Each result contains:
744
+
745
+ ```ts
746
+ {
747
+ workerId,
748
+ status,
749
+ response?,
750
+ error?
751
+ }
752
+ ```
753
+
754
+ Possible result:
755
+
756
+ ```ts
757
+ [
758
+ {
759
+ workerId: 1,
760
+ status: "fulfilled",
761
+ response: {
762
+ status: "OK",
763
+ },
764
+ },
765
+ {
766
+ workerId: 2,
767
+ status: "rejected",
768
+ error: ...
769
+ },
770
+ {
771
+ workerId: 3,
772
+ status: "fulfilled",
773
+ response: {
774
+ status: "OK",
775
+ },
776
+ },
777
+ ]
778
+ ```
779
+
780
+ This is generally safer for operational broadcasts where one failed worker should not hide successful responses from the others.
781
+
782
+ ---
783
+
784
+ # emitToAll()
785
+
786
+ Broadcasts an event to all workers.
787
+
788
+ ```ts
789
+ const responses = await manager.emitToAll(event, ...args);
790
+ ```
791
+
792
+ Example:
793
+
794
+ ```ts
795
+ const responses = await manager.emitToAll("get-status");
796
+ ```
797
+
798
+ Conceptually:
799
+
800
+ ```text
801
+ Pair 1 -> Worker 1
802
+ /
803
+ Manager ----------- Pair 2 -> Worker 2
804
+ \
805
+ Pair 3 -> Worker 3
806
+ ```
807
+
808
+ Each response includes the associated worker ID.
809
+
810
+ ---
811
+
812
+ # emitToAllSettled()
813
+
814
+ Broadcasts an event to all workers and returns the result of every worker independently.
815
+
816
+ ```ts
817
+ const responses = await manager.emitToAllSettled("get-status");
818
+ ```
819
+
820
+ Example:
821
+
822
+ ```ts
823
+ for (const result of responses) {
824
+ if (result.status === "fulfilled") {
825
+ console.log(`Worker ${result.workerId}`, result.response);
826
+ } else {
827
+ console.error(`Worker ${result.workerId} failed`, result.error);
828
+ }
829
+ }
830
+ ```
831
+
832
+ This is useful for:
833
+
834
+ - health checks;
835
+ - worker status collection;
836
+ - cluster diagnostics;
837
+ - configuration refresh;
838
+ - coordinated operations across many workers.
839
+
840
+ ---
841
+
842
+ # emitNoResponseToAll()
843
+
844
+ Broadcasts a fire-and-forget event to every worker.
845
+
846
+ ```ts
847
+ await manager.emitNoResponseToAll(event, ...args);
848
+ ```
849
+
850
+ Example:
851
+
852
+ ```ts
853
+ await manager.emitNoResponseToAll("refresh-config");
854
+ ```
855
+
856
+ Conceptually:
857
+
858
+ ```text
859
+ Manager
860
+ |
861
+ +----> Worker 1
862
+ +----> Worker 2
863
+ +----> Worker 3
864
+ ```
865
+
866
+ No worker response is expected.
867
+
868
+ ---
869
+
870
+ # Automatic Worker Cleanup
871
+
872
+ By default:
873
+
874
+ ```ts
875
+ autoRemoveOnWorkerExit !== false;
876
+ ```
877
+
878
+ causes the manager to register:
879
+
880
+ ```ts
881
+ worker.once("exit", ...)
882
+ ```
883
+
884
+ When the worker exits, the manager automatically removes its pair.
885
+
886
+ Conceptually:
887
+
888
+ ```text
889
+ Worker exits
890
+ |
891
+ v
892
+ RemoveWorker(workerId)
893
+ |
894
+ +-- remove exit listener
895
+ +-- pair.Stop()
896
+ +-- remove pair
897
+ +-- remove worker
898
+ ```
899
+
900
+ This prevents dead worker references from remaining in the collection.
901
+
902
+ ---
903
+
904
+ # Disabling Automatic Cleanup
905
+
906
+ If another process manager already owns worker cleanup:
907
+
908
+ ```ts
909
+ const manager = new IPCMessageHandlerPairManager({
910
+ logger,
911
+ requestResponseMessageTimeout: 5000,
912
+ namespace: "STS_WORKERS",
913
+ autoRemoveOnWorkerExit: false,
914
+ });
915
+ ```
916
+
917
+ The caller must then explicitly perform:
918
+
919
+ ```ts
920
+ manager.RemoveWorker(worker.id);
921
+ ```
922
+
923
+ ---
924
+
925
+ # Stop()
926
+
927
+ Stops every managed pair and clears the active worker collection.
928
+
929
+ ```ts
930
+ manager.Stop();
931
+ ```
932
+
933
+ Conceptually:
934
+
935
+ ```text
936
+ Before:
937
+
938
+ Manager
939
+ ├── Pair 1 -> Worker 1
940
+ ├── Pair 2 -> Worker 2
941
+ └── Pair 3 -> Worker 3
942
+
943
+
944
+ manager.Stop()
945
+
946
+
947
+ After:
948
+
949
+ Manager
950
+ ```
951
+
952
+ Each pair is stopped before removal.
953
+
954
+ ---
955
+
956
+ ## Event Registrations Are Retained
957
+
958
+ `Stop()` intentionally does not clear manager-level event registrations.
959
+
960
+ For example:
961
+
962
+ ```ts
963
+ manager.on("get-status", callback);
964
+
965
+ manager.Stop();
966
+
967
+ manager.AddWorker(newWorker);
968
+ ```
969
+
970
+ `newWorker` still receives the `"get-status"` registration.
971
+
972
+ This allows a manager to be stopped and reused without rebuilding all of its event configuration.
973
+
974
+ ---
975
+
976
+ # Typical Primary Process Usage
977
+
978
+ ```ts
979
+ import cluster from "node:cluster";
980
+
981
+ import { IPCMessageHandlerPairManager } from "./ipcMessageHandlerPairManager.js";
982
+
983
+ const manager = new IPCMessageHandlerPairManager({
984
+ logger,
985
+ requestResponseMessageTimeout: 5000,
986
+ namespace: "STS_WORKERS",
987
+ });
988
+
989
+ manager.on("worker-ready", (data, callback) => {
990
+ console.log("Worker ready:", data);
991
+
992
+ callback({
993
+ status: "OK",
994
+ });
995
+ });
996
+
997
+ const worker1 = cluster.fork();
998
+ const worker2 = cluster.fork();
999
+ const worker3 = cluster.fork();
1000
+
1001
+ manager.AddWorker(worker1);
1002
+ manager.AddWorker(worker2);
1003
+ manager.AddWorker(worker3);
1004
+ ```
1005
+
1006
+ Result:
1007
+
1008
+ ```text
1009
+ manager
1010
+
1011
+ ├── worker1 -> pair1
1012
+ ├── worker2 -> pair2
1013
+ └── worker3 -> pair3
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ # Send to One Worker
1019
+
1020
+ ```ts
1021
+ const response = await manager.emitTo(worker2.id, "get-status");
1022
+
1023
+ console.log(response);
1024
+ ```
1025
+
1026
+ Only `worker2` receives the event.
1027
+
1028
+ ---
1029
+
1030
+ # Send to All Workers
1031
+
1032
+ ```ts
1033
+ const responses = await manager.emitToAll("get-status");
1034
+
1035
+ console.log(responses);
1036
+ ```
1037
+
1038
+ Every registered worker receives the event.
1039
+
1040
+ ---
1041
+
1042
+ # Safe Broadcast
1043
+
1044
+ ```ts
1045
+ const results = await manager.emitToAllSettled("get-status");
1046
+
1047
+ for (const result of results) {
1048
+ if (result.status === "fulfilled") {
1049
+ console.log(`Worker ${result.workerId} OK`, result.response);
1050
+ } else {
1051
+ console.log(`Worker ${result.workerId} FAILED`, result.error);
1052
+ }
1053
+ }
1054
+ ```
1055
+
1056
+ ---
1057
+
1058
+ # Worker-Side Usage
1059
+
1060
+ The worker does **not** use `IPCMessageHandlerPairManager`.
1061
+
1062
+ Each worker uses one `IPCMessageHandlerPair`.
1063
+
1064
+ ```ts
1065
+ import { IPCMessageHandlerPair } from "./ipcMessageHandlerPair.js";
1066
+
1067
+ const ipc = new IPCMessageHandlerPair({
1068
+ logger,
1069
+ requestResponseMessageTimeout: 5000,
1070
+ namespace: "STS_WORKERS",
1071
+ role: "CLIENT",
1072
+ });
1073
+
1074
+ ipc.on("get-status", (callback) => {
1075
+ callback({
1076
+ pid: process.pid,
1077
+ status: "OK",
1078
+ });
1079
+ });
1080
+
1081
+ ipc.Start();
1082
+ ```
1083
+
1084
+ Architecture:
1085
+
1086
+ ```text
1087
+ PRIMARY
1088
+
1089
+ IPCMessageHandlerPairManager
1090
+ |
1091
+ +-- Pair 1
1092
+ +-- Pair 2
1093
+ +-- Pair 3
1094
+
1095
+
1096
+ WORKERS
1097
+
1098
+ Worker 1
1099
+ |
1100
+ +-- IPCMessageHandlerPair(CLIENT)
1101
+
1102
+ Worker 2
1103
+ |
1104
+ +-- IPCMessageHandlerPair(CLIENT)
1105
+
1106
+ Worker 3
1107
+ |
1108
+ +-- IPCMessageHandlerPair(CLIENT)
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ # Difference from IPCMessageHandlerPair
1114
+
1115
+ `IPCMessageHandlerPairManager` and `IPCMessageHandlerPair` have deliberately different responsibilities.
1116
+
1117
+ ## IPCMessageHandlerPair
1118
+
1119
+ Represents one IPC relationship.
1120
+
1121
+ ```text
1122
+ Primary <----------> Worker
1123
+ ```
1124
+
1125
+ It handles:
1126
+
1127
+ - one worker;
1128
+ - message sending;
1129
+ - message receiving;
1130
+ - event dispatch;
1131
+ - request/response communication.
1132
+
1133
+ ---
1134
+
1135
+ ## IPCMessageHandlerPairManager
1136
+
1137
+ Represents a collection of those relationships.
1138
+
1139
+ ```text
1140
+ Manager
1141
+
1142
+ ├── Pair -> Worker 1
1143
+ ├── Pair -> Worker 2
1144
+ └── Pair -> Worker 3
1145
+ ```
1146
+
1147
+ It handles:
1148
+
1149
+ - worker collection;
1150
+ - pair creation;
1151
+ - pair removal;
1152
+ - targeted worker selection;
1153
+ - broadcasts;
1154
+ - event propagation;
1155
+ - worker cleanup.
1156
+
1157
+ ---
1158
+
1159
+ # Difference from IPCMessageHandler
1160
+
1161
+ `IPCMessageHandlerPairManager` replaces the multi-worker responsibilities that were previously built directly into `IPCMessageHandler`.
1162
+
1163
+ ## IPCMessageHandler
1164
+
1165
+ The old model combines:
1166
+
1167
+ ```text
1168
+ worker collection
1169
+ +
1170
+ IPC connection handling
1171
+ +
1172
+ broadcasting
1173
+ +
1174
+ event handling
1175
+ ```
1176
+
1177
+ inside one class.
1178
+
1179
+ Conceptually:
1180
+
1181
+ ```text
1182
+ IPCMessageHandler
1183
+
1184
+ ├── Worker 1
1185
+ ├── Worker 2
1186
+ └── Worker 3
1187
+ ```
1188
+
1189
+ ---
1190
+
1191
+ ## IPCMessageHandlerPairManager
1192
+
1193
+ The pair-manager model separates the responsibilities:
1194
+
1195
+ ```text
1196
+ IPCMessageHandlerPairManager
1197
+
1198
+ ├── IPCMessageHandlerPair -> Worker 1
1199
+ ├── IPCMessageHandlerPair -> Worker 2
1200
+ └── IPCMessageHandlerPair -> Worker 3
1201
+ ```
1202
+
1203
+ The manager manages the collection.
1204
+
1205
+ Each pair manages one connection.
1206
+
1207
+ ---
1208
+
1209
+ # Comparison
1210
+
1211
+ | Feature | IPCMessageHandlerPairManager | IPCMessageHandlerPair | IPCMessageHandler |
1212
+ | -------------------------------- | ---------------------------- | -------------------------- | ---------------------------- |
1213
+ | Primary purpose | Manage many IPC pairs | Manage one IPC connection | Manage many workers directly |
1214
+ | Relationship | One-to-many via pairs | One-to-one | One-to-many |
1215
+ | Stores worker collection | Yes | No | Yes |
1216
+ | Stores one worker | Per pair | Yes | No |
1217
+ | Creates pair per worker | Yes | N/A | No |
1218
+ | Target one worker | Yes | Naturally | Not its main model |
1219
+ | Broadcast | Yes | No | Yes |
1220
+ | Event propagation | Across all pairs | One pair only | Internal collection |
1221
+ | Future-worker event registration | Yes | N/A | N/A |
1222
+ | Automatic worker cleanup | Yes | No manager-level lifecycle | Manual client removal |
1223
+ | Separation of concerns | Strong | Strong | More combined |
1224
+ | Recommended server abstraction | Yes | Used by manager | Older combined model |
1225
+
1226
+ ---
1227
+
1228
+ # Recommended Responsibility Model
1229
+
1230
+ The intended architecture is:
1231
+
1232
+ ```text
1233
+ Process / Worker Manager
1234
+ |
1235
+ v
1236
+ IPCMessageHandlerPairManager
1237
+ |
1238
+ +-------------------+
1239
+ | |
1240
+ v v
1241
+ IPCMessageHandlerPair IPCMessageHandlerPair
1242
+ | |
1243
+ v v
1244
+ MessagingManager MessagingManager
1245
+ | |
1246
+ v v
1247
+ Worker 1 Worker 2
1248
+ ```
1249
+
1250
+ Responsibilities should remain:
1251
+
1252
+ ```text
1253
+ Process Manager
1254
+ -> decides when workers should exist
1255
+
1256
+ IPCMessageHandlerPairManager
1257
+ -> manages IPC relationships for those workers
1258
+
1259
+ IPCMessageHandlerPair
1260
+ -> manages one IPC relationship
1261
+
1262
+ MessagingManager
1263
+ -> manages the message protocol
1264
+ ```
1265
+
1266
+ ---
1267
+
1268
+ # When to Use IPCMessageHandlerPairManager
1269
+
1270
+ Use `IPCMessageHandlerPairManager` on the cluster primary/server side when:
1271
+
1272
+ - multiple workers exist;
1273
+ - each worker should have its own `IPCMessageHandlerPair`;
1274
+ - individual workers must be addressable;
1275
+ - broadcasts are required;
1276
+ - worker lifecycle cleanup should be centralized;
1277
+ - event registrations should automatically apply to every pair.
1278
+
1279
+ The key distinction is:
1280
+
1281
+ ```text
1282
+ IPCMessageHandlerPair
1283
+ = "this worker connection"
1284
+
1285
+ IPCMessageHandlerPairManager
1286
+ = "all of my worker connections"
1287
+ ```
1288
+
1289
+ This makes the pair manager the natural server-side abstraction for managing IPC communication across a Node.js worker cluster.