@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,702 @@
1
+ # IPCMessageHandlerPair
2
+
3
+ `IPCMessageHandlerPair` provides IPC-based request/response and event messaging between a Node.js cluster primary process and **one specific worker process**.
4
+
5
+ It is built on top of `MessagingManager` and exposes an event-oriented API using `on()`, `off()`, `emit()`, and `emitNoResponse()`.
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Use `IPCMessageHandlerPair` when each handler instance should represent a **single primary-to-worker IPC relationship**.
12
+
13
+ Typical topology:
14
+
15
+ ```text
16
+ Primary <----------> Worker
17
+ ```
18
+
19
+ For multiple workers, create or maintain multiple pair instances:
20
+
21
+ ```text
22
+ Worker Manager
23
+
24
+ ├── Pair 1 <-------> Worker 1
25
+ ├── Pair 2 <-------> Worker 2
26
+ └── Pair 3 <-------> Worker 3
27
+ ```
28
+
29
+ The class does not maintain an internal collection of workers.
30
+
31
+ ---
32
+
33
+ ## Key Characteristics
34
+
35
+ - Supports `SERVER` and `CLIENT` roles.
36
+ - Designed around **one server/primary to one worker**.
37
+ - Stores one worker reference.
38
+ - Server-side `Start(worker)` associates the handler with that worker.
39
+ - Server-side `SendMessage()` sends to that one worker.
40
+ - Server-side `emit()` sends to that one worker.
41
+ - Uses `MessagingManager` for request/response message handling.
42
+ - Supports event registration through `on()` and `off()`.
43
+ - Supports request messages that expect a response.
44
+ - Supports fire-and-forget request messages through `emitNoResponse()`.
45
+ - Does not perform server-side broadcast management.
46
+ - Does not maintain a collection of cluster workers.
47
+
48
+ ---
49
+
50
+ # Constructor
51
+
52
+ ```ts
53
+ new IPCMessageHandlerPair(options);
54
+ ```
55
+
56
+ ### Options
57
+
58
+ ```ts
59
+ export interface IPCMessageHandlerPairOptions {
60
+ logger: ISTSLogger;
61
+ requestResponseMessageTimeout: number;
62
+ namespace: string;
63
+ role: "SERVER" | "CLIENT";
64
+ ignoreEvents?: string[];
65
+ }
66
+ ```
67
+
68
+ ### `logger`
69
+
70
+ Logger used when event emission or message processing fails.
71
+
72
+ ### `requestResponseMessageTimeout`
73
+
74
+ Maximum time allowed for request/response messages.
75
+
76
+ ### `namespace`
77
+
78
+ Namespace supplied to the underlying `MessagingManager`.
79
+
80
+ ### `role`
81
+
82
+ Determines which side of the IPC relationship is configured.
83
+
84
+ ```ts
85
+ role: "SERVER";
86
+ ```
87
+
88
+ Represents the primary/server side.
89
+
90
+ ```ts
91
+ role: "CLIENT";
92
+ ```
93
+
94
+ Represents the worker/client side.
95
+
96
+ ### `ignoreEvents`
97
+
98
+ Optional list of event names whose errors should be ignored by `emit()`.
99
+
100
+ ---
101
+
102
+ # Server Role
103
+
104
+ When configured with:
105
+
106
+ ```ts
107
+ role: "SERVER";
108
+ ```
109
+
110
+ the handler is associated with one worker.
111
+
112
+ The class stores:
113
+
114
+ ```ts
115
+ #worker;
116
+ ```
117
+
118
+ and the worker relationship is established through:
119
+
120
+ ```ts
121
+ Start(worker);
122
+ ```
123
+
124
+ ---
125
+
126
+ # Server-side Message Receiver
127
+
128
+ The server-side implementation registers a `"message"` listener directly on the worker.
129
+
130
+ Conceptually:
131
+
132
+ ```ts
133
+ worker.on("message", handler);
134
+ ```
135
+
136
+ Incoming messages are passed to:
137
+
138
+ ```ts
139
+ MessagingManager.ProcessMessage(...)
140
+ ```
141
+
142
+ with the associated worker supplied as message context.
143
+
144
+ This gives the pair a direct relationship between:
145
+
146
+ ```text
147
+ one IPCMessageHandlerPair
148
+ |
149
+ v
150
+ one Worker
151
+ ```
152
+
153
+ ---
154
+
155
+ # Client Role
156
+
157
+ When configured with:
158
+
159
+ ```ts
160
+ role: "CLIENT";
161
+ ```
162
+
163
+ the handler runs inside the worker process.
164
+
165
+ Outgoing messages use Node.js process IPC:
166
+
167
+ ```ts
168
+ process.send(...)
169
+ ```
170
+
171
+ Incoming messages use:
172
+
173
+ ```ts
174
+ process.on("message", ...)
175
+ ```
176
+
177
+ The client side therefore communicates with its parent process.
178
+
179
+ ---
180
+
181
+ # Start()
182
+
183
+ ```ts
184
+ pair.Start(worker);
185
+ ```
186
+
187
+ For a server-side pair, `Start()` associates the handler with one worker.
188
+
189
+ Example:
190
+
191
+ ```ts
192
+ const pair = new IPCMessageHandlerPair({
193
+ logger,
194
+ requestResponseMessageTimeout: 5000,
195
+ namespace: "worker-1",
196
+ role: "SERVER",
197
+ });
198
+
199
+ pair.Start(worker);
200
+ ```
201
+
202
+ The handler then stores the worker reference and starts the underlying `MessagingManager` with it.
203
+
204
+ ---
205
+
206
+ ## Client-side Start()
207
+
208
+ A worker/client does not need to supply a worker object:
209
+
210
+ ```ts
211
+ pair.Start();
212
+ ```
213
+
214
+ The client communicates with its parent through `process.send()` and `process.on("message")`.
215
+
216
+ ---
217
+
218
+ # Stop()
219
+
220
+ ```ts
221
+ pair.Stop();
222
+ ```
223
+
224
+ When a worker is associated with the pair, `Stop()`:
225
+
226
+ 1. Stops `MessagingManager` using the worker context.
227
+ 2. Removes the worker message receiver through the configured messaging callbacks.
228
+ 3. Clears the stored worker reference.
229
+
230
+ Conceptually:
231
+
232
+ ```text
233
+ Before:
234
+
235
+ IPCMessageHandlerPair ---> Worker
236
+
237
+ After Stop():
238
+
239
+ IPCMessageHandlerPair Worker
240
+ ```
241
+
242
+ ---
243
+
244
+ # worker
245
+
246
+ ```ts
247
+ pair.worker;
248
+ ```
249
+
250
+ Returns the worker currently associated with the pair.
251
+
252
+ The value is `null` when no server-side worker is attached.
253
+
254
+ ---
255
+
256
+ # SendMessage()
257
+
258
+ ```ts
259
+ await pair.SendMessage(payload);
260
+ ```
261
+
262
+ Sends a request message through the underlying `MessagingManager`.
263
+
264
+ ## Server behavior
265
+
266
+ If a worker has been supplied through:
267
+
268
+ ```ts
269
+ pair.Start(worker);
270
+ ```
271
+
272
+ the message is sent specifically to that worker.
273
+
274
+ ```text
275
+ Primary
276
+ |
277
+ v
278
+ Worker
279
+ ```
280
+
281
+ There is no iteration over a worker collection.
282
+
283
+ There is no built-in broadcast.
284
+
285
+ There is no `Promise.all()` aggregation of responses from multiple workers.
286
+
287
+ ## Client behavior
288
+
289
+ When no worker is stored, the message is sent using the client-side messaging configuration, which uses:
290
+
291
+ ```ts
292
+ process.send(...)
293
+ ```
294
+
295
+ to communicate with the primary process.
296
+
297
+ ---
298
+
299
+ # Event Registration
300
+
301
+ ## on()
302
+
303
+ ```ts
304
+ pair.on(eventName, callback);
305
+ ```
306
+
307
+ Registers an IPC event callback.
308
+
309
+ Example:
310
+
311
+ ```ts
312
+ pair.on("get-status", (callback) => {
313
+ callback({
314
+ pid: process.pid,
315
+ status: "OK",
316
+ });
317
+ });
318
+ ```
319
+
320
+ Only one event callback is retained for a given event name.
321
+
322
+ Registering the same event again replaces the existing callback.
323
+
324
+ ---
325
+
326
+ ## off()
327
+
328
+ ```ts
329
+ pair.off(eventName);
330
+ ```
331
+
332
+ Removes an event callback.
333
+
334
+ ---
335
+
336
+ # emit()
337
+
338
+ ```ts
339
+ pair.emit(eventName, ...args, callback);
340
+ ```
341
+
342
+ Sends an event that expects a response.
343
+
344
+ Example:
345
+
346
+ ```ts
347
+ pair.emit("get-status", (response) => {
348
+ console.log(response);
349
+ });
350
+ ```
351
+
352
+ Internally, the event is represented as a message payload containing:
353
+
354
+ ```ts
355
+ {
356
+ __eventName: eventName,
357
+ args: [...]
358
+ }
359
+ ```
360
+
361
+ The final argument is used as the response callback.
362
+
363
+ ---
364
+
365
+ ## Server-side emit behavior
366
+
367
+ A server-side `emit()` targets **the single worker associated with this pair**.
368
+
369
+ ```text
370
+ IPCMessageHandlerPair
371
+ |
372
+ v
373
+ Worker
374
+ ```
375
+
376
+ If the primary needs to emit the same event to three workers, the surrounding manager must invoke the corresponding three pair instances.
377
+
378
+ For example:
379
+
380
+ ```ts
381
+ for (const pair of workerPairs.values()) {
382
+ pair.emit("refresh-config", callback);
383
+ }
384
+ ```
385
+
386
+ Broadcasting is therefore a responsibility of the owner of the pair collection rather than of `IPCMessageHandlerPair` itself.
387
+
388
+ ---
389
+
390
+ # emitNoResponse()
391
+
392
+ ```ts
393
+ await pair.emitNoResponse(eventName, ...args);
394
+ ```
395
+
396
+ Sends a fire-and-forget event.
397
+
398
+ No response callback is required.
399
+
400
+ Conceptually:
401
+
402
+ ```text
403
+ Pair ------> remote process
404
+ ```
405
+
406
+ Unlike `IPCMessageHandler`, this class does not iterate over an internal worker collection.
407
+
408
+ ---
409
+
410
+ # Message Processing
411
+
412
+ Incoming request messages are handled by `#processPayload()`.
413
+
414
+ The payload identifies the event using:
415
+
416
+ ```ts
417
+ requestPayload.__eventName;
418
+ ```
419
+
420
+ The registered callback is retrieved from the internal event map.
421
+
422
+ Supported request types include:
423
+
424
+ ```text
425
+ REQUEST
426
+ REQUEST_NO_RESPONSE
427
+ ```
428
+
429
+ ---
430
+
431
+ ## REQUEST
432
+
433
+ The event callback is given the request arguments followed by a response callback.
434
+
435
+ Conceptually:
436
+
437
+ ```ts
438
+ pair.on("get-status", (...args, callback) => {
439
+ callback({
440
+ status: "OK",
441
+ });
442
+ });
443
+ ```
444
+
445
+ ---
446
+
447
+ ## REQUEST_NO_RESPONSE
448
+
449
+ The registered callback is invoked with only the supplied event arguments.
450
+
451
+ No response is expected.
452
+
453
+ ---
454
+
455
+ # Typical Primary Usage
456
+
457
+ ```ts
458
+ const pair = new IPCMessageHandlerPair({
459
+ logger,
460
+ requestResponseMessageTimeout: 5000,
461
+ namespace: "worker-1",
462
+ role: "SERVER",
463
+ });
464
+
465
+ pair.Start(worker);
466
+
467
+ pair.emit("get-status", (response) => {
468
+ console.log(response);
469
+ });
470
+ ```
471
+
472
+ The message is sent only to the worker associated with `pair`.
473
+
474
+ ---
475
+
476
+ # Typical Worker Usage
477
+
478
+ ```ts
479
+ const pair = new IPCMessageHandlerPair({
480
+ logger,
481
+ requestResponseMessageTimeout: 5000,
482
+ namespace: "worker-1",
483
+ role: "CLIENT",
484
+ });
485
+
486
+ pair.on("get-status", (callback) => {
487
+ callback({
488
+ pid: process.pid,
489
+ status: "OK",
490
+ });
491
+ });
492
+
493
+ pair.Start();
494
+ ```
495
+
496
+ ---
497
+
498
+ # Managing Multiple Workers
499
+
500
+ `IPCMessageHandlerPair` intentionally does not maintain a worker collection.
501
+
502
+ A higher-level process manager can maintain the collection instead.
503
+
504
+ For example:
505
+
506
+ ```ts
507
+ const workerPairs = new Map<number, IPCMessageHandlerPair>();
508
+ ```
509
+
510
+ Conceptually:
511
+
512
+ ```text
513
+ WorkerProcessManager
514
+
515
+ ├── Worker 1
516
+ │ └── IPCMessageHandlerPair
517
+
518
+ ├── Worker 2
519
+ │ └── IPCMessageHandlerPair
520
+
521
+ └── Worker 3
522
+ └── IPCMessageHandlerPair
523
+ ```
524
+
525
+ This separates two responsibilities:
526
+
527
+ ```text
528
+ WorkerProcessManager
529
+ -> owns workers
530
+ -> handles worker lifecycle
531
+ -> performs broadcast operations
532
+
533
+ IPCMessageHandlerPair
534
+ -> owns one IPC relationship
535
+ -> handles messages
536
+ -> handles request/response correlation
537
+ ```
538
+
539
+ ---
540
+
541
+ # Difference from IPCMessageHandler
542
+
543
+ This is the most important distinction.
544
+
545
+ ## IPCMessageHandlerPair is one-to-one
546
+
547
+ One pair represents one worker relationship:
548
+
549
+ ```text
550
+ Primary <----------> Worker
551
+ ```
552
+
553
+ It stores one worker:
554
+
555
+ ```ts
556
+ #worker;
557
+ ```
558
+
559
+ and the server associates that worker with:
560
+
561
+ ```ts
562
+ Start(worker);
563
+ ```
564
+
565
+ A message from the server is sent only to that worker.
566
+
567
+ ---
568
+
569
+ ## IPCMessageHandler is one-to-many
570
+
571
+ `IPCMessageHandler` owns a collection:
572
+
573
+ ```ts
574
+ #clients;
575
+ ```
576
+
577
+ Workers are added with:
578
+
579
+ ```ts
580
+ AddClient(worker);
581
+ ```
582
+
583
+ and removed with:
584
+
585
+ ```ts
586
+ RemoveClient(id);
587
+ ```
588
+
589
+ Its topology is:
590
+
591
+ ```text
592
+ Worker 1
593
+ /
594
+ Primary ---------- Worker 2
595
+ \
596
+ Worker 3
597
+ ```
598
+
599
+ When the server calls:
600
+
601
+ ```ts
602
+ SendMessage(...)
603
+ ```
604
+
605
+ the handler iterates through its worker collection and sends the message to all registered workers.
606
+
607
+ ---
608
+
609
+ # Responsibility Difference
610
+
611
+ This distinction is architectural, not merely a difference in method names.
612
+
613
+ ## IPCMessageHandlerPair
614
+
615
+ ```text
616
+ one connection
617
+ +
618
+ messaging
619
+ ```
620
+
621
+ The class represents a single IPC relationship.
622
+
623
+ A higher-level manager is responsible for:
624
+
625
+ ```text
626
+ worker collection
627
+ broadcasting
628
+ worker lifecycle
629
+ ```
630
+
631
+ ## IPCMessageHandler
632
+
633
+ ```text
634
+ worker collection
635
+ +
636
+ messaging
637
+ +
638
+ broadcasting
639
+ ```
640
+
641
+ Those responsibilities are combined inside the handler.
642
+
643
+ ---
644
+
645
+ # Comparison
646
+
647
+ | Feature | IPCMessageHandlerPair | IPCMessageHandler |
648
+ | ------------------------------------------ | ------------------------ | -------------------------- |
649
+ | Relationship | One-to-one | One-to-many |
650
+ | Number of server-side workers per instance | One | Many |
651
+ | Worker storage | Single `#worker` | `#clients` collection |
652
+ | Attach worker | `Start(worker)` | `AddClient(worker)` |
653
+ | Detach worker | `Stop()` | `RemoveClient(id)` |
654
+ | Server `SendMessage()` | One worker | All workers |
655
+ | Server `emit()` | One worker | All workers |
656
+ | Server `emitNoResponse()` | One target | All workers |
657
+ | Multiple-response aggregation | No | Yes, using `Promise.all()` |
658
+ | Built-in worker collection | No | Yes |
659
+ | Built-in broadcasting | No | Yes |
660
+ | External manager naturally owns workers | Yes | Not required |
661
+ | Best abstraction | "this worker connection" | "my worker cluster" |
662
+
663
+ ---
664
+
665
+ # When to Use IPCMessageHandlerPair
666
+
667
+ Use `IPCMessageHandlerPair` when the surrounding process manager already knows which workers exist and should remain responsible for worker lifecycle.
668
+
669
+ It is particularly appropriate when the desired abstraction is:
670
+
671
+ ```text
672
+ "Send this message to this worker."
673
+ ```
674
+
675
+ rather than:
676
+
677
+ ```text
678
+ "Send this message to all workers."
679
+ ```
680
+
681
+ It also provides a clean structure when each worker has other worker-specific state.
682
+
683
+ For example:
684
+
685
+ ```ts
686
+ interface WorkerRecord {
687
+ worker: Worker;
688
+ ipc: IPCMessageHandlerPair;
689
+ startedAt: Date;
690
+ state: WorkerState;
691
+ }
692
+ ```
693
+
694
+ The process manager can then choose whether to:
695
+
696
+ - address one worker;
697
+ - address a subset of workers;
698
+ - broadcast to every worker;
699
+ - remove a worker;
700
+ - replace a worker.
701
+
702
+ The IPC transport itself remains focused on the single primary/worker communication relationship.