pp-command-bus 1.4.0 → 2.0.0

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.
Files changed (195) hide show
  1. package/README.md +402 -1113
  2. package/dist/command-bus/command-bus.spec.js +144 -370
  3. package/dist/command-bus/command-bus.spec.js.map +1 -1
  4. package/dist/command-bus/command.d.ts +23 -5
  5. package/dist/command-bus/command.js +20 -34
  6. package/dist/command-bus/command.js.map +1 -1
  7. package/dist/command-bus/config/command-bus-config.d.ts +75 -21
  8. package/dist/command-bus/config/command-bus-config.js +99 -58
  9. package/dist/command-bus/config/command-bus-config.js.map +1 -1
  10. package/dist/command-bus/config/command-bus-config.spec.js +174 -100
  11. package/dist/command-bus/config/command-bus-config.spec.js.map +1 -1
  12. package/dist/command-bus/index.d.ts +39 -52
  13. package/dist/command-bus/index.js +133 -126
  14. package/dist/command-bus/index.js.map +1 -1
  15. package/dist/command-bus/logging/command-logger.d.ts +2 -0
  16. package/dist/command-bus/logging/command-logger.js +7 -0
  17. package/dist/command-bus/logging/command-logger.js.map +1 -1
  18. package/dist/command-bus/logging/command-logger.spec.js +49 -14
  19. package/dist/command-bus/logging/command-logger.spec.js.map +1 -1
  20. package/dist/command-bus/serialization/index.d.ts +6 -0
  21. package/dist/command-bus/serialization/index.js +9 -0
  22. package/dist/command-bus/serialization/index.js.map +1 -0
  23. package/dist/command-bus/serialization/msgpack-serializer.d.ts +26 -0
  24. package/dist/command-bus/serialization/msgpack-serializer.js +70 -0
  25. package/dist/command-bus/serialization/msgpack-serializer.js.map +1 -0
  26. package/dist/command-bus/serialization/msgpack-serializer.spec.js +223 -0
  27. package/dist/command-bus/serialization/msgpack-serializer.spec.js.map +1 -0
  28. package/dist/command-bus/serialization/serializer.interface.d.ts +21 -0
  29. package/dist/command-bus/serialization/serializer.interface.js +3 -0
  30. package/dist/command-bus/serialization/serializer.interface.js.map +1 -0
  31. package/dist/command-bus/transport/consumer-loop.d.ts +45 -0
  32. package/dist/command-bus/transport/consumer-loop.js +90 -0
  33. package/dist/command-bus/transport/consumer-loop.js.map +1 -0
  34. package/dist/command-bus/transport/consumer-loop.spec.js +216 -0
  35. package/dist/command-bus/transport/consumer-loop.spec.js.map +1 -0
  36. package/dist/command-bus/transport/index.d.ts +21 -0
  37. package/dist/command-bus/transport/index.js +23 -0
  38. package/dist/command-bus/transport/index.js.map +1 -0
  39. package/dist/command-bus/transport/message-processor.d.ts +59 -0
  40. package/dist/command-bus/transport/message-processor.js +111 -0
  41. package/dist/command-bus/transport/message-processor.js.map +1 -0
  42. package/dist/command-bus/transport/message-processor.spec.js +185 -0
  43. package/dist/command-bus/transport/message-processor.spec.js.map +1 -0
  44. package/dist/command-bus/transport/pending-recovery.d.ts +54 -0
  45. package/dist/command-bus/transport/pending-recovery.js +139 -0
  46. package/dist/command-bus/transport/pending-recovery.js.map +1 -0
  47. package/dist/command-bus/transport/pending-recovery.spec.js +176 -0
  48. package/dist/command-bus/transport/pending-recovery.spec.js.map +1 -0
  49. package/dist/command-bus/transport/redis-codec.d.ts +24 -0
  50. package/dist/command-bus/transport/redis-codec.js +33 -0
  51. package/dist/command-bus/transport/redis-codec.js.map +1 -0
  52. package/dist/command-bus/transport/redis-codec.spec.js +53 -0
  53. package/dist/command-bus/transport/redis-codec.spec.js.map +1 -0
  54. package/dist/command-bus/transport/redis-streams-transport.d.ts +91 -0
  55. package/dist/command-bus/transport/redis-streams-transport.js +134 -0
  56. package/dist/command-bus/transport/redis-streams-transport.js.map +1 -0
  57. package/dist/command-bus/transport/redis-streams-transport.spec.js +420 -0
  58. package/dist/command-bus/transport/redis-streams-transport.spec.js.map +1 -0
  59. package/dist/command-bus/transport/rpc-handler.d.ts +39 -0
  60. package/dist/command-bus/transport/rpc-handler.js +87 -0
  61. package/dist/command-bus/transport/rpc-handler.js.map +1 -0
  62. package/dist/command-bus/transport/rpc-handler.spec.js +157 -0
  63. package/dist/command-bus/transport/rpc-handler.spec.js.map +1 -0
  64. package/dist/command-bus/transport/stream-consumer.d.ts +89 -0
  65. package/dist/command-bus/transport/stream-consumer.js +181 -0
  66. package/dist/command-bus/transport/stream-consumer.js.map +1 -0
  67. package/dist/command-bus/transport/stream-consumer.spec.js +284 -0
  68. package/dist/command-bus/transport/stream-consumer.spec.js.map +1 -0
  69. package/dist/command-bus/transport/stream-producer.d.ts +23 -0
  70. package/dist/command-bus/transport/stream-producer.js +70 -0
  71. package/dist/command-bus/transport/stream-producer.js.map +1 -0
  72. package/dist/command-bus/transport/stream-producer.spec.js +125 -0
  73. package/dist/command-bus/transport/stream-producer.spec.js.map +1 -0
  74. package/dist/command-bus/transport/transport.interface.d.ts +87 -0
  75. package/dist/command-bus/transport/transport.interface.js +3 -0
  76. package/dist/command-bus/transport/transport.interface.js.map +1 -0
  77. package/dist/command-bus/types/index.d.ts +9 -80
  78. package/dist/examples/rpc-throughput.demo.js +24 -22
  79. package/dist/examples/rpc-throughput.demo.js.map +1 -1
  80. package/dist/examples/rpc.demo.js +47 -53
  81. package/dist/examples/rpc.demo.js.map +1 -1
  82. package/dist/index.d.ts +8 -5
  83. package/dist/index.js +6 -4
  84. package/dist/index.js.map +1 -1
  85. package/dist/pp-command-bus-2.0.0.tgz +0 -0
  86. package/dist/shared/redis/connection-pool.d.ts +54 -0
  87. package/dist/shared/redis/connection-pool.js +117 -0
  88. package/dist/shared/redis/connection-pool.js.map +1 -0
  89. package/dist/shared/redis/connection-pool.spec.js +114 -0
  90. package/dist/shared/redis/connection-pool.spec.js.map +1 -0
  91. package/dist/shared/redis/index.d.ts +5 -3
  92. package/dist/shared/redis/index.js +6 -4
  93. package/dist/shared/redis/index.js.map +1 -1
  94. package/dist/shared/redis/rpc-connection-pool.d.ts +61 -0
  95. package/dist/shared/redis/rpc-connection-pool.js +154 -0
  96. package/dist/shared/redis/rpc-connection-pool.js.map +1 -0
  97. package/dist/shared/redis/rpc-connection-pool.spec.d.ts +1 -0
  98. package/dist/shared/redis/rpc-connection-pool.spec.js +173 -0
  99. package/dist/shared/redis/rpc-connection-pool.spec.js.map +1 -0
  100. package/dist/shared/types.d.ts +0 -4
  101. package/dist/shared/utils/error-utils.d.ts +8 -0
  102. package/dist/shared/utils/error-utils.js +14 -0
  103. package/dist/shared/utils/error-utils.js.map +1 -0
  104. package/package.json +12 -12
  105. package/dist/command-bus/config/auto-config-optimizer.d.ts +0 -35
  106. package/dist/command-bus/config/auto-config-optimizer.js +0 -52
  107. package/dist/command-bus/config/auto-config-optimizer.js.map +0 -1
  108. package/dist/command-bus/config/auto-config-optimizer.spec.js +0 -42
  109. package/dist/command-bus/config/auto-config-optimizer.spec.js.map +0 -1
  110. package/dist/command-bus/job/index.d.ts +0 -6
  111. package/dist/command-bus/job/index.js +0 -15
  112. package/dist/command-bus/job/index.js.map +0 -1
  113. package/dist/command-bus/job/job-options-builder.d.ts +0 -21
  114. package/dist/command-bus/job/job-options-builder.js +0 -58
  115. package/dist/command-bus/job/job-options-builder.js.map +0 -1
  116. package/dist/command-bus/job/job-options-builder.spec.js +0 -156
  117. package/dist/command-bus/job/job-options-builder.spec.js.map +0 -1
  118. package/dist/command-bus/job/job-processor.d.ts +0 -39
  119. package/dist/command-bus/job/job-processor.js +0 -203
  120. package/dist/command-bus/job/job-processor.js.map +0 -1
  121. package/dist/command-bus/job/job-processor.spec.js +0 -437
  122. package/dist/command-bus/job/job-processor.spec.js.map +0 -1
  123. package/dist/command-bus/queue/index.d.ts +0 -5
  124. package/dist/command-bus/queue/index.js +0 -13
  125. package/dist/command-bus/queue/index.js.map +0 -1
  126. package/dist/command-bus/queue/queue-manager.d.ts +0 -56
  127. package/dist/command-bus/queue/queue-manager.js +0 -163
  128. package/dist/command-bus/queue/queue-manager.js.map +0 -1
  129. package/dist/command-bus/queue/queue-manager.spec.js +0 -371
  130. package/dist/command-bus/queue/queue-manager.spec.js.map +0 -1
  131. package/dist/command-bus/rpc/index.d.ts +0 -11
  132. package/dist/command-bus/rpc/index.js +0 -19
  133. package/dist/command-bus/rpc/index.js.map +0 -1
  134. package/dist/command-bus/rpc/payload-compression.service.d.ts +0 -51
  135. package/dist/command-bus/rpc/payload-compression.service.js +0 -218
  136. package/dist/command-bus/rpc/payload-compression.service.js.map +0 -1
  137. package/dist/command-bus/rpc/payload-compression.service.spec.js +0 -379
  138. package/dist/command-bus/rpc/payload-compression.service.spec.js.map +0 -1
  139. package/dist/command-bus/rpc/rpc-coordinator.d.ts +0 -96
  140. package/dist/command-bus/rpc/rpc-coordinator.js +0 -500
  141. package/dist/command-bus/rpc/rpc-coordinator.js.map +0 -1
  142. package/dist/command-bus/rpc/rpc-coordinator.spec.js +0 -622
  143. package/dist/command-bus/rpc/rpc-coordinator.spec.js.map +0 -1
  144. package/dist/command-bus/rpc/rpc-job-cancellation.service.d.ts +0 -82
  145. package/dist/command-bus/rpc/rpc-job-cancellation.service.js +0 -180
  146. package/dist/command-bus/rpc/rpc-job-cancellation.service.js.map +0 -1
  147. package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js +0 -286
  148. package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js.map +0 -1
  149. package/dist/command-bus/worker/index.d.ts +0 -10
  150. package/dist/command-bus/worker/index.js +0 -19
  151. package/dist/command-bus/worker/index.js.map +0 -1
  152. package/dist/command-bus/worker/worker-benchmark.d.ts +0 -71
  153. package/dist/command-bus/worker/worker-benchmark.js +0 -203
  154. package/dist/command-bus/worker/worker-benchmark.js.map +0 -1
  155. package/dist/command-bus/worker/worker-benchmark.spec.js +0 -310
  156. package/dist/command-bus/worker/worker-benchmark.spec.js.map +0 -1
  157. package/dist/command-bus/worker/worker-metrics-collector.d.ts +0 -98
  158. package/dist/command-bus/worker/worker-metrics-collector.js +0 -242
  159. package/dist/command-bus/worker/worker-metrics-collector.js.map +0 -1
  160. package/dist/command-bus/worker/worker-orchestrator.d.ts +0 -70
  161. package/dist/command-bus/worker/worker-orchestrator.js +0 -339
  162. package/dist/command-bus/worker/worker-orchestrator.js.map +0 -1
  163. package/dist/command-bus/worker/worker-orchestrator.spec.js +0 -712
  164. package/dist/command-bus/worker/worker-orchestrator.spec.js.map +0 -1
  165. package/dist/examples/auto-config.demo.d.ts +0 -9
  166. package/dist/examples/auto-config.demo.js +0 -106
  167. package/dist/examples/auto-config.demo.js.map +0 -1
  168. package/dist/examples/rpc-compression.demo.d.ts +0 -5
  169. package/dist/examples/rpc-compression.demo.js +0 -363
  170. package/dist/examples/rpc-compression.demo.js.map +0 -1
  171. package/dist/examples/rpc-resilience.demo.d.ts +0 -11
  172. package/dist/examples/rpc-resilience.demo.js +0 -235
  173. package/dist/examples/rpc-resilience.demo.js.map +0 -1
  174. package/dist/pp-command-bus-1.4.0.tgz +0 -0
  175. package/dist/shared/config/base-config.d.ts +0 -54
  176. package/dist/shared/config/base-config.js +0 -114
  177. package/dist/shared/config/base-config.js.map +0 -1
  178. package/dist/shared/config/base-config.spec.js +0 -204
  179. package/dist/shared/config/base-config.spec.js.map +0 -1
  180. package/dist/shared/config/index.d.ts +0 -1
  181. package/dist/shared/config/index.js +0 -9
  182. package/dist/shared/config/index.js.map +0 -1
  183. package/dist/shared/redis/redis-connection-factory.d.ts +0 -66
  184. package/dist/shared/redis/redis-connection-factory.js +0 -113
  185. package/dist/shared/redis/redis-connection-factory.js.map +0 -1
  186. /package/dist/command-bus/{config/auto-config-optimizer.spec.d.ts → serialization/msgpack-serializer.spec.d.ts} +0 -0
  187. /package/dist/command-bus/{job/job-options-builder.spec.d.ts → transport/consumer-loop.spec.d.ts} +0 -0
  188. /package/dist/command-bus/{job/job-processor.spec.d.ts → transport/message-processor.spec.d.ts} +0 -0
  189. /package/dist/command-bus/{queue/queue-manager.spec.d.ts → transport/pending-recovery.spec.d.ts} +0 -0
  190. /package/dist/command-bus/{rpc/payload-compression.service.spec.d.ts → transport/redis-codec.spec.d.ts} +0 -0
  191. /package/dist/command-bus/{rpc/rpc-coordinator.spec.d.ts → transport/redis-streams-transport.spec.d.ts} +0 -0
  192. /package/dist/command-bus/{rpc/rpc-job-cancellation.service.spec.d.ts → transport/rpc-handler.spec.d.ts} +0 -0
  193. /package/dist/command-bus/{worker/worker-benchmark.spec.d.ts → transport/stream-consumer.spec.d.ts} +0 -0
  194. /package/dist/command-bus/{worker/worker-orchestrator.spec.d.ts → transport/stream-producer.spec.d.ts} +0 -0
  195. /package/dist/shared/{config/base-config.spec.d.ts → redis/connection-pool.spec.d.ts} +0 -0
package/README.md CHANGED
@@ -1,239 +1,103 @@
1
1
  # PP Command Bus
2
2
 
3
- Distributed Command Bus library supporting RPC and job queuing with BullMQ for Redis/DragonflyDB.
3
+ Rozproszona biblioteka Command Bus oparta na **Redis Streams** z serializacją **MessagePack** i RPC przez **LPUSH/BRPOP**. Zoptymalizowana pod **DragonflyDB**.
4
+
5
+ ## Spis treści
6
+
7
+ - [Opis](#opis)
8
+ - [Architektura](#architektura)
9
+ - [Instalacja](#instalacja)
10
+ - [Szybki start](#szybki-start)
11
+ - [API](#api)
12
+ - [Konfiguracja](#konfiguracja)
13
+ - [Flow danych](#flow-danych)
14
+ - [Komponenty](#komponenty)
15
+ - [Struktura projektu](#struktura-projektu)
16
+ - [Testowanie](#testowanie)
17
+ - [Changelog](#changelog)
4
18
 
5
19
  ## Opis
6
20
 
7
- **pp-command-bus** to zaawansowana biblioteka do obsługi rozproszonych komend zgodna ze wzorcem **CQRS (Command Query Responsibility Segregation)**. Zapewnia wysoką wydajność, automatyczną optymalizację i zaawansowane funkcje produkcyjne.
8
-
9
- ### Kluczowe Cechy
10
-
11
- - **Fire-and-forget commands** - wysyłanie komend bez oczekiwania na wynik
12
- - **RPC (Remote Procedure Call)** - synchroniczne wywołania przez Redis Pub/Sub z oczekiwaniem na odpowiedź
13
- - **Automatyczna kompresja** - gzip dla payloadów RPC >1KB (konfigurowalne)
14
- - **Auto-optymalizacja** - dynamiczne dostosowywanie concurrency na podstawie CPU/RAM
15
- - **Process isolation** - izolacja odpowiedzi RPC między procesami Node.js
16
- - ✅ **Job queuing** - kolejkowanie zadań z retry, backoff i delayed execution
17
- - ✅ **BullMQ integration** - wydajna kolejka zadań na Redis/DragonflyDB
18
- - ✅ **TypeScript** - pełne wsparcie typów i strict mode
19
- - **Memory leak protection** - zaawansowana diagnostyka i cleanup
20
- - ✅ **Command logging** - opcjonalne logowanie komend do plików JSONL
21
- - ✅ **RPC Job Cancellation** - automatyczne usuwanie niepodjętych jobów RPC przy timeout
22
- - ✅ **Modularna architektura** - komponenty zgodne z zasadami SOLID, DDD i SRP
23
-
24
- ## Architektura Systemu
25
-
26
- ### Diagram Komponentów
27
-
28
- ```
29
- ┌─────────────────────────────────────────────────────────────────────┐
30
- CommandBus │
31
- │ ┌────────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
32
- QueueManager │ │ RpcCoordinator│ │ WorkerOrchestrator │ │
33
- - Cache kolejek│ │ - Pub/Sub │ │ - Dynamiczne concurrency
34
- │ │ - BullMQ Queue │ │ - Process ID │ │ - Worker benchmark │ │
35
- └────────┬───────┘ └──────┬───────┘ └────────┬────────────────┘
36
- │ │ │ │
37
-
38
- ┌────────▼──────────────────▼────────────────────▼───────────────┐
39
- │ │ JobProcessor (Command Handler Execution) │ │
40
- │ - Kompresja/dekompresja payloadów │
41
- - Wykonywanie handlerów
42
- - Wysyłanie odpowiedzi RPC przez Pub/Sub │ │
43
- └──────────────────────────┬──────────────────────────────────────┘
44
- └─────────────────────────────┼────────────────────────────────────────┘
45
-
46
- ┌─────────▼──────────┐
47
- PayloadCompression
48
- Service
49
- │ - gzip compression │
50
- │ - threshold: 1KB
51
- └─────────────────────┘
52
- ```
21
+ **pp-command-bus** to biblioteka do obsługi rozproszonych komend zgodna ze wzorcem **CQRS**. Zapewnia:
22
+
23
+ - **Fire-and-forget dispatch** — komendy wysyłane przez `XADD` do Redis Streams
24
+ - **Batch dispatch** — wiele komend w jednym pipeline Redis
25
+ - **RPC (request/response)** synchroniczne wywołania z timeout przez `LPUSH/BRPOP`
26
+ - **Consumer Groups** dokładnie jedno przetworzenie komendy (exactly-once delivery)
27
+ - **Dead Letter Queue** wiadomości po przekroczeniu prób trafiają do `dlq:{stream}`
28
+ - **Auto-recovery** automatyczne przejmowanie stalled wiadomości przez `XPENDING/XCLAIM`
29
+ - **MessagePack** binarna serializacja z natywną obsługą `Date` (extension type)
30
+
31
+ ## Architektura
32
+
33
+ ### Diagram komponentów
34
+
35
+ ```
36
+ ┌─────────────────────────────────────────────────────────────┐
37
+ │ CommandBus │
38
+ │ dispatch() | dispatchBatch() | call() | handle() | close() │
39
+ └────────────────────────────┬────────────────────────────────┘
40
+
41
+ ┌────────┴────────┐
42
+ │ ITransport │
43
+ └────────┬────────┘
44
+
45
+ ┌────────────────────────────┴────────────────────────────────┐
46
+ RedisStreamsTransport
47
+ (fasada / kompozycja)
48
+ ├─────────────┬──────────────┬──────────────┬─────────────────┤
49
+ │ │ │
50
+ ┌───────────┴─┐ ┌─────────┴──────┐ ┌────┴─────┐ ┌─────────┴──┐
51
+ Stream Stream │ Rpc │ │ Pending │
52
+ Producer │ Consumer │ │ Handler │ │ Recovery │
53
+ (XADD) (koordynator) │ (BRPOP) │ │ (XCLAIM) │
54
+ └─────────────┘ └───────┬────────┘ └──────────┘ └────────────┘
55
+
56
+ ┌──────────┴──────────┐
57
+ │ │
58
+ │ ┌────────┴─────────┐ ┌────────┴────────┐ │
59
+ │ Message │ │ Consumer │ │
60
+ │ │ Processor │ │ Loop │ │
61
+ (parse/XACK/RPC) │ (XREADGROUP) │ │
62
+ └──────────────────┘ └─────────────────┘
63
+ ├─────────────────────────────────────────────────────────────┤
64
+ Warstwa połączeń
65
+ │ ┌─────────────────────┐ ┌──────────────────────┐ │
66
+ │ │ RedisConnectionPool │ │ RpcConnectionPool │ │
67
+ │ │ (round-robin, eager)│ │ (bounded, lazy, BRPOP)│ │
68
+ │ └─────────────────────┘ └──────────────────────┘ │
69
+ ├─────────────────────────────────────────────────────────────┤
70
+ │ Warstwa serializacji │
71
+ │ ┌─────────────────────┐ ┌──────────────────────┐ │
72
+ │ │ MsgpackSerializer │ │ RedisCodec │ │
73
+ │ │ (Date extension) │ │ (base64 encode) │ │
74
+ │ └─────────────────────┘ └──────────────────────┘ │
75
+ └─────────────────────────────────────────────────────────────┘
76
+ ```
77
+
78
+ ### Segregacja interfejsów (ISP)
79
+
80
+ ```typescript
81
+ interface IStreamProducer {
82
+ enqueue(streamName: string, data: Buffer): Promise<string>;
83
+ enqueueBatch(entries: Array<{ streamName: string; data: Buffer }>): Promise<string[]>;
84
+ }
53
85
 
54
- ### Główne Serwisy
55
-
56
- #### 1. **CommandBus** (główna klasa orkiestrująca)
57
- - **Odpowiedzialność**: Główny punkt wejścia dla użytkownika, orkiestracja wszystkich komponentów
58
- - **Metody publiczne**:
59
- - `dispatch(command)` - wysyłanie fire-and-forget
60
- - `call(command, timeout)` - synchroniczne RPC
61
- - `handle(commandClass, handler)` - rejestracja handlerów
62
- - `close()` - graceful shutdown
63
- - **Zarządzanie połączeniami**: 3 dedykowane połączenia Redis (Queue, Worker, RPC)
64
-
65
- #### 2. **RpcCoordinator** (zarządzanie RPC)
66
- - **Odpowiedzialność**: Zarządzanie cyklem życia wywołań RPC przez Redis Pub/Sub
67
- - **Kluczowe funkcje**:
68
- - **Shared Subscriber** - jeden subscriber dla wszystkich RPC calls (pattern matching)
69
- - **Process Isolation** - UUID procesu Node.js dla izolacji odpowiedzi między procesami
70
- - **Timeout Management** - automatyczne cleanup po timeout
71
- - **Job Cancellation** - usuwanie niepodjętych jobów przy timeout (optymalizacja zasobów)
72
- - **Multiplexing** - routing odpowiedzi do odpowiednich promises
73
- - **Kanały**: `rpc:response:{processId}:{correlationId}`
74
-
75
- #### 3. **WorkerOrchestrator** (orkiestracja workerów)
76
- - **Odpowiedzialność**: Zarządzanie workerami BullMQ i dynamiczna optymalizacja
77
- - **Kluczowe funkcje**:
78
- - **WorkerBenchmark** - automatyczny benchmark przy rejestracji handlera
79
- - **WorkerMetricsCollector** - event-driven metrics collection
80
- - **Dynamic Concurrency** - dostosowywanie concurrency +/-20% co 30s
81
- - **Event Handlers** - obsługa zdarzeń: active, completed, failed, stalled
82
- - **Limity concurrency**: min 10, max 2000
83
-
84
- #### 4. **JobProcessor** (wykonywanie handlerów)
85
- - **Odpowiedzialność**: Wykonywanie handlerów komend i obsługa odpowiedzi RPC
86
- - **Flow przetwarzania**:
87
- 1. Dekompresja payloadu (jeśli skompresowany)
88
- 2. Sprawdzenie flagi cancellation (pomijanie anulowanych RPC)
89
- 3. Rekonstrukcja obiektów Date
90
- 4. Opcjonalne logowanie komendy
91
- 5. Wykonanie handlera
92
- 6. Wysłanie odpowiedzi RPC przez Pub/Sub (jeśli RPC)
93
- - **Kompresja odpowiedzi**: automatyczna kompresja przez PayloadCompressionService
94
- - **RPC Cancellation**: pomijanie jobów oznaczonych jako anulowane (timeout)
95
-
96
- #### 5. **QueueManager** (zarządzanie kolejkami)
97
- - **Odpowiedzialność**: Cache kolejek BullMQ dla optymalizacji pamięci
98
- - **Funkcje**:
99
- - `getOrCreateQueue(commandName)` - lazy loading kolejek
100
- - `closeAllQueues()` - graceful shutdown wszystkich kolejek
101
- - **Naming**: `{CommandName}` jako nazwa kolejki
102
-
103
- #### 6. **PayloadCompressionService** (kompresja)
104
- - **Odpowiedzialność**: Automatyczna kompresja/dekompresja payloadów gzip
105
- - **Threshold**: 1024 bajty (1KB) domyślnie, konfigurowalne przez ENV
106
- - **Metody**:
107
- - `compressCommand(command)` - dodaje flagę `__compressed`
108
- - `decompressCommand(command)` - dekompresja i usunięcie flagi
109
- - `compress(data)` - generyczna kompresja do base64
110
- - `decompress(data, compressed)` - generyczna dekompresja
111
- - **Współdzielony serwis**: jedna instancja dla całego CommandBus
112
-
113
- #### 7. **RpcJobCancellationService** (anulowanie jobów RPC)
114
- - **Odpowiedzialność**: Zarządzanie anulowaniem niepodjętych jobów RPC przy timeout
115
- - **Kluczowe funkcje**:
116
- - **markAsCancelled(correlationId)** - oznacza job jako anulowany w Redis
117
- - **isCancelled(correlationId)** - sprawdza flagę cancellation
118
- - **clearCancellation(correlationId)** - usuwa flagę po przetworzeniu/usunięciu
119
- - **tryRemoveJob(jobId, queueName, callback)** - próba usunięcia joba z kolejki
120
- - **TTL kluczy Redis**: 24 godziny (automatyczne wygaśnięcie)
121
- - **Graceful degradation**: błędy Redis nie blokują przetwarzania (zwraca false)
122
- - **Klucze Redis**: `rpc:cancelled:{correlationId}`
123
-
124
- #### 8. **CommandLogger** (opcjonalne logowanie)
125
- - **Odpowiedzialność**: Persystencja komend do plików JSONL
126
- - **Format**: `{timestamp}.jsonl` - rotacja co godzinę
127
- - **Zawartość**: pełny payload komendy z metadanymi
128
- - **Aktywacja**: przez ENV `COMMAND_BUS_LOG=./command-logs`
129
-
130
- #### 9. **AutoConfigOptimizer** (auto-optymalizacja)
131
- - **Odpowiedzialność**: Obliczanie optymalnego concurrency na podstawie zasobów systemowych
132
- - **Heurystyka**:
133
- - I/O-heavy workload (Redis/BullMQ)
134
- - `concurrency = CPU cores * 2 + (availableMemory / 512MB)`
135
- - Zakładane 512MB RAM per worker
136
- - **Aktywacja**: domyślnie włączone (ENV `COMMAND_BUS_AUTO_OPTIMIZE=false` wyłącza)
137
-
138
- #### 10. **RedisConnectionFactory** (fabryka połączeń Redis)
139
- - **Odpowiedzialność**: Tworzenie połączeń Redis z wbudowaną obsługą błędów i eventów
140
- - **Zgodność z SRP**: CommandBus nie zarządza bezpośrednio połączeniami
141
- - **Kluczowe funkcje**:
142
- - `create(options, name)` - tworzy połączenie z pełną obsługą eventów
143
- - `createForWorker(options, name)` - dodaje `maxRetriesPerRequest: null` dla BullMQ
144
- - Obsługa eventów: `error`, `close`, `reconnecting`, `connect`, `ready`
145
- - Formatowanie błędów `AggregateError` (Node.js dual-stack IPv4/IPv6)
146
- - **Event Handlers**:
147
- - `on('error')` - logowanie błędów połączenia (zapobiega `Unhandled error event`)
148
- - `on('close')` - informacja o zamknięciu połączenia
149
- - `on('reconnecting')` - informacja o ponownym łączeniu
150
- - `on('connect')` / `on('ready')` - potwierdzenie nawiązania połączenia
151
-
152
- ### Flow Przepływu Danych
153
-
154
- #### Flow 1: dispatch() - Fire-and-forget
86
+ interface IStreamConsumer {
87
+ consume(streamName: string, groupName: string, handler: ConsumerHandler): Promise<void>;
88
+ }
155
89
 
156
- ```
157
- User Code
158
-
159
- ├─→ 1. commandBus.dispatch(command)
160
-
161
- ├─→ 2. PayloadCompressionService.compressCommand(command)
162
- │ └─→ Jeśli payload >1KB → gzip → base64 → __compressed: true
163
-
164
- ├─→ 3. QueueManager.getOrCreateQueue(commandName)
165
- │ └─→ Cache hit/miss → zwraca Queue
166
-
167
- ├─→ 4. queue.add(commandName, compressedCommand, options)
168
- │ └─→ BullMQ dodaje job do Redis
169
-
170
- └─→ 5. Promise<void> resolved (nie czekamy na wynik)
171
-
172
- Worker Side (asynchronicznie)
173
-
174
- ├─→ 6. Worker pobiera job z kolejki
175
-
176
- ├─→ 7. JobProcessor.process(job)
177
- │ ├─→ Dekompresja (jeśli __compressed)
178
- │ ├─→ Rekonstrukcja Date
179
- │ ├─→ Opcjonalne logowanie (CommandLogger)
180
- │ └─→ Wykonanie handlera
181
-
182
- └─→ 8. Worker kończy job (success/fail)
183
- ```
90
+ interface IRpcTransport {
91
+ rpcCall(commandName: string, data: Buffer, timeout: number): Promise<Buffer>;
92
+ rpcRespond(responseKey: string, data: Buffer, ttl: number): Promise<void>;
93
+ }
184
94
 
185
- #### Flow 2: call() - RPC przez Redis Pub/Sub
95
+ interface IClosable {
96
+ close(): Promise<void>;
97
+ }
186
98
 
187
- ```
188
- User Code
189
-
190
- ├─→ 1. commandBus.call(command, timeout)
191
-
192
- ├─→ 2. RpcCoordinator.registerCall(correlationId, commandName, timeout)
193
- │ ├─→ Oczekiwanie na gotowość shared subscriber (5s timeout)
194
- │ ├─→ Utworzenie Promise<T> dla odpowiedzi
195
- │ ├─→ Zapisanie pending call w Map
196
- │ └─→ Zwrócenie responsePromise (bez blokowania)
197
-
198
- ├─→ 3. PayloadCompressionService.compressCommand(command)
199
- │ └─→ Jeśli payload >1KB → gzip → __compressed: true
200
-
201
- ├─→ 4. RpcCoordinator.prepareRpcCommand(compressedCommand)
202
- │ └─→ Dodaje __rpcMetadata: { correlationId, responseChannel, timestamp }
203
-
204
- ├─→ 5. QueueManager.getOrCreateQueue(commandName)
205
- │ └─→ Cache hit/miss → zwraca Queue
206
-
207
- ├─→ 6. queue.add(commandName, commandWithMetadata, options)
208
- │ └─→ BullMQ dodaje job do Redis
209
-
210
- └─→ 7. await responsePromise (czeka na odpowiedź z Worker)
211
-
212
- Worker Side
213
-
214
- ├─→ 8. Worker pobiera job z kolejki
215
-
216
- ├─→ 9. JobProcessor.process(job)
217
- │ ├─→ Dekompresja payloadu (jeśli __compressed)
218
- │ ├─→ Rekonstrukcja Date
219
- │ ├─→ Wykonanie handlera → result
220
- │ │
221
- │ └─→ 10. JobProcessor.sendRpcResponse(rpcMetadata, result, null)
222
- │ ├─→ PayloadCompressionService.compress({ correlationId, result, error })
223
- │ ├─→ Wrapper: { data, compressed }
224
- │ └─→ redis.publish(responseChannel, JSON.stringify(wrapper))
225
-
226
- Shared Subscriber (RpcCoordinator)
227
-
228
- ├─→ 11. pmessage event → handleRpcMessage(channel, message)
229
- │ ├─→ Ekstraktuj correlationId z channel
230
- │ ├─→ Weryfikuj processInstanceId (process isolation)
231
- │ ├─→ Znajdź pending call w Map
232
- │ ├─→ PayloadCompressionService.decompress(wrapper.data, wrapper.compressed)
233
- │ ├─→ resolve(result) lub reject(error)
234
- │ └─→ Cleanup: clearTimeout, delete z Map
235
-
236
- └─→ 12. User Code otrzymuje wynik → Promise<T> resolved
99
+ // Pełny interfejs transportu — kompozycja segregowanych interfejsów
100
+ interface ITransport extends IStreamProducer, IStreamConsumer, IRpcTransport, IClosable {}
237
101
  ```
238
102
 
239
103
  ## Instalacja
@@ -244,979 +108,404 @@ npm install pp-command-bus
244
108
 
245
109
  ### Wymagania
246
110
 
247
- - Node.js >= 14
248
- - Redis >= 5 lub DragonflyDB
249
- - TypeScript >= 4.5 (opcjonalnie)
250
-
251
- ## Szybki start
111
+ - **Node.js** >= 18
112
+ - **Redis** >= 6.2 lub **DragonflyDB** >= 1.0
113
+ - **TypeScript** >= 5.0 (opcjonalnie, pełne typowanie)
252
114
 
253
- ### 1. Konfiguracja
115
+ ### Zależności
254
116
 
255
- ```typescript
256
- import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
117
+ | Pakiet | Opis |
118
+ |--------|------|
119
+ | `ioredis` | Klient Redis z pipelining, Cluster, Sentinel |
120
+ | `@msgpack/msgpack` | Binarna serializacja z extension types |
257
121
 
258
- // Konfiguracja CommandBus
259
- const config = new CommandBusConfig({
260
- redisUrl: 'redis://localhost:6379',
261
- logger: console, // ILogger interface
262
- logLevel: 'log', // debug | log | warn | error
263
- concurrency: 5,
264
- maxAttempts: 1,
265
- });
266
-
267
- // Utwórz instancję CommandBus
268
- const commandBus = new CommandBus(config);
269
- ```
122
+ ## Szybki start
270
123
 
271
- ### 2. Definiowanie komend
124
+ ### Definiowanie komendy
272
125
 
273
126
  ```typescript
274
- class CreateUserCommand extends Command {
275
- constructor(
276
- public readonly email: string,
277
- public readonly name: string,
278
- ) {
279
- super();
127
+ import { Command } from 'pp-command-bus';
128
+
129
+ class CreateUserCommand extends Command<{ name: string; email: string }> {
130
+ constructor(payload: { name: string; email: string }) {
131
+ super(payload);
280
132
  }
281
133
  }
282
134
  ```
283
135
 
284
- ### 3. Rejestracja handlerów
285
-
286
- ```typescript
287
- commandBus.handle(CreateUserCommand, async (command) => {
288
- console.log(`Creating user: ${command.name} (${command.email})`);
289
-
290
- // Twoja logika biznesowa
291
- const user = await createUser(command.email, command.name);
292
-
293
- // Zwróć wynik (opcjonalnie)
294
- return { userId: user.id };
295
- });
296
- ```
297
-
298
- ### 4. Wysyłanie komend (Fire-and-forget)
299
-
300
- ```typescript
301
- const command = new CreateUserCommand('jan.kowalski@example.com', 'Jan Kowalski');
302
-
303
- // Wyślij komendę bez oczekiwania na wynik
304
- await commandBus.dispatch(command);
305
- ```
306
-
307
- ### 5. RPC - wywołania synchroniczne
308
-
309
- ```typescript
310
- // Wywołaj komendę i poczekaj na wynik
311
- const result = await commandBus.call<{ userId: string }>(command, 5000); // timeout 5s
312
-
313
- console.log(`User created with ID: ${result.userId}`);
314
- ```
315
-
316
- ## Zmienne Środowiskowe
317
-
318
- Biblioteka wspiera konfigurację poprzez zmienne środowiskowe z prefiksem `COMMAND_BUS_` (z fallbackiem do starszych nazw `EVENT_BUS_*`):
319
-
320
- ### Kompletna Lista Zmiennych
321
-
322
- | Zmienna | Typ | Wartość Domyślna | Opis |
323
- |---------|-----|------------------|------|
324
- | `REDIS_URL` | string | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB (wspiera username, password, db) |
325
- | `REDIS_RETRY_DELAY` | number | `5000` | Opóźnienie między próbami reconnect do Redis w milisekundach |
326
- | `REDIS_MAX_RETRIES` | number | `0` | Maksymalna liczba prób reconnect (0 = nieskończoność) |
327
- | `LOG_LEVEL` | enum | `log` | Poziom logowania: `debug`, `log`, `warn`, `error` |
328
- | `COMMAND_BUS_CONCURRENCY` | number | `1` (lub auto) | Liczba równoległych workerów do przetwarzania komend |
329
- | `COMMAND_BUS_MAX_ATTEMPTS` | number | `1` | Maksymalna liczba prób przetworzenia zadania |
330
- | `COMMAND_BUS_BACKOFF_DELAY` | number | `2000` | Opóźnienie między próbami w milisekundach |
331
- | `COMMAND_BUS_QUEUE_MODE` | enum | `fifo` | Tryb przetwarzania kolejki: `fifo` (First In First Out) lub `lifo` (Last In First Out) |
332
- | `COMMAND_BUS_LOG` | string | _(puste)_ | Ścieżka do katalogu logów komend (JSONL format, rotacja co godzinę) |
333
- | `COMMAND_BUS_AUTO_OPTIMIZE` | boolean | `true` | Włącz auto-optymalizację concurrency na podstawie CPU/RAM |
334
- | `COMMAND_BUS_COMPRESSION_THRESHOLD` | number | `1024` | Próg kompresji gzip dla payloadów RPC w bajtach (1KB domyślnie) |
335
-
336
- ### Fallback do Starszych Nazw
337
-
338
- Dla kompatybilności wstecznej obsługiwane są również prefiksy `EVENT_BUS_*`:
339
-
340
- - `EVENT_BUS_CONCURRENCY` → `COMMAND_BUS_CONCURRENCY`
341
- - `EVENT_BUS_MAX_ATTEMPTS` → `COMMAND_BUS_MAX_ATTEMPTS`
342
- - `EVENT_BUS_BACKOFF_DELAY` → `COMMAND_BUS_BACKOFF_DELAY`
343
- - `EVENT_BUS_QUEUE_MODE` → `COMMAND_BUS_QUEUE_MODE`
344
- - `EVENT_BUS_LOG` → `COMMAND_BUS_LOG`
345
-
346
- ### Przykład Konfiguracji .env
347
-
348
- ```bash
349
- # Połączenie Redis
350
- REDIS_URL=redis://username:password@localhost:6379/0
351
-
352
- # Strategia reconnect Redis (stałe opóźnienie)
353
- REDIS_RETRY_DELAY=5000 # 5 sekund między próbami (domyślnie)
354
- REDIS_MAX_RETRIES=0 # 0 = nieskończoność (domyślnie)
355
-
356
- # Poziom logowania
357
- LOG_LEVEL=log # debug | log | warn | error
358
-
359
- # Auto-optymalizacja (domyślnie włączona)
360
- COMMAND_BUS_AUTO_OPTIMIZE=true
361
-
362
- # Concurrency (opcjonalnie - auto-optymalizacja ustawi optymalną wartość)
363
- # COMMAND_BUS_CONCURRENCY=10
364
-
365
- # Retry i backoff
366
- COMMAND_BUS_MAX_ATTEMPTS=1 # Maksymalna liczba prób
367
- COMMAND_BUS_BACKOFF_DELAY=3000 # Opóźnienie między próbami (3s)
368
-
369
- # Tryb kolejki
370
- COMMAND_BUS_QUEUE_MODE=fifo # fifo lub lifo
371
-
372
- # Kompresja payloadów (próg w bajtach)
373
- COMMAND_BUS_COMPRESSION_THRESHOLD=2048 # 2KB (domyślnie 1KB)
374
-
375
- # Logowanie komend do plików
376
- COMMAND_BUS_LOG=./command-logs # Ścieżka do katalogu logów
377
- ```
378
-
379
- ### Priorytety Konfiguracji
380
-
381
- 1. **Parametry konstruktora** - najwyższy priorytet
382
- 2. **Zmienne środowiskowe** - średni priorytet
383
- 3. **Wartości domyślne** - najniższy priorytet
136
+ ### Konfiguracja i inicjalizacja
384
137
 
385
138
  ```typescript
386
- // Przykład: parametry konstruktora nadpisują ENV
387
- const config = new CommandBusConfig({
388
- redisUrl: 'redis://localhost:6379',
389
- concurrency: 20, // Nadpisuje COMMAND_BUS_CONCURRENCY
390
- autoOptimize: false, // Wyłącza auto-optymalizację
391
- });
392
- ```
393
-
394
- ## Konfiguracja zaawansowana
395
-
396
- ### Auto-optymalizacja Concurrency
139
+ import { CommandBus, CommandBusConfig } from 'pp-command-bus';
397
140
 
398
- Auto-optymalizacja automatycznie oblicza optymalną wartość concurrency na podstawie zasobów systemowych:
399
-
400
- ```typescript
401
- // Auto-optymalizacja włączona (domyślnie)
402
- const config = new CommandBusConfig({
403
- redisUrl: 'redis://localhost:6379',
404
- autoOptimize: true, // Domyślnie true
405
- // concurrency zostanie obliczone jako: CPU cores * 2 + (availableMemory / 512MB)
406
- });
407
-
408
- // Wyłączenie auto-optymalizacji
409
- const config2 = new CommandBusConfig({
410
- redisUrl: 'redis://localhost:6379',
411
- autoOptimize: false,
412
- concurrency: 10, // Ręczna wartość
413
- });
414
- ```
415
-
416
- **Algorytm auto-optymalizacji**:
417
- ```
418
- concurrency = (CPU cores * 2) + Math.floor(availableMemory / 512MB)
419
-
420
- Przykład:
421
- - 8 CPU cores
422
- - 16GB RAM dostępne
423
- - concurrency = (8 * 2) + Math.floor(16384 / 512) = 16 + 32 = 48
424
- ```
425
-
426
- ### Kompresja Payloadów
427
-
428
- Automatyczna kompresja gzip dla payloadów RPC większych niż threshold:
429
-
430
- ```typescript
431
- const config = new CommandBusConfig({
432
- redisUrl: 'redis://localhost:6379',
433
- compressionThreshold: 2048, // 2KB (domyślnie 1KB)
434
- });
435
-
436
- // Przykład: payload 3KB zostanie automatycznie skompresowany
437
- const largeCommand = new ProcessReportCommand(largeData); // 3KB
438
- const result = await commandBus.call(largeCommand); // Automatyczna kompresja/dekompresja
439
- ```
440
-
441
- **Korzyści kompresji**:
442
- - Redukcja transferu danych przez Redis
443
- - Szybsze przesyłanie dużych payloadów
444
- - Niższe zużycie pamięci Redis
445
- - Transparent dla użytkownika (automatyczna dekompresja)
446
-
447
- ### Command Logging
448
-
449
- Logowanie komend do plików JSONL (rotacja co godzinę):
450
-
451
- ```typescript
452
- const config = new CommandBusConfig({
453
- redisUrl: 'redis://localhost:6379',
454
- commandLog: './command-logs', // Ścieżka do katalogu logów
455
- });
456
-
457
- // Struktura plików:
458
- // ./command-logs/2025-01-27T10.jsonl
459
- // ./command-logs/2025-01-27T11.jsonl
460
- ```
461
-
462
- **Format JSONL (JSON Lines)**:
463
- ```json
464
- {"__name":"CreateUserCommand","__id":"uuid","__time":1706347200000,"email":"jan@example.com","name":"Jan"}
465
- {"__name":"ProcessOrderCommand","__id":"uuid","__time":1706347201000,"orderId":"12345"}
466
- ```
467
-
468
- ### Opcje Redis
469
-
470
- ```typescript
471
- const config = new CommandBusConfig({
472
- redisUrl: 'redis://username:password@localhost:6379/0',
473
- // Parsuje się do:
474
- // - host: localhost
475
- // - port: 6379
476
- // - username: username (opcjonalnie)
477
- // - password: password (opcjonalnie)
478
- // - db: 0 (opcjonalnie)
479
- });
480
- ```
481
-
482
- ### Concurrency i Retry
483
-
484
- ```typescript
485
- const config = new CommandBusConfig({
486
- redisUrl: 'redis://localhost:6379',
487
- concurrency: 10, // Liczba równoległych workerów (lub auto)
488
- maxAttempts: 5, // Maksymalna liczba prób przetworzenia zadania
489
- backoffDelay: 3000, // Opóźnienie między próbami (3s)
490
- });
491
- ```
492
-
493
- ### Tryb kolejki
494
-
495
- ```typescript
496
- const config = new CommandBusConfig({
497
- redisUrl: 'redis://localhost:6379',
498
- queueMode: 'fifo', // 'fifo' (First In First Out) lub 'lifo' (Last In First Out)
499
- });
500
- ```
501
-
502
- ### Custom Logger
503
-
504
- Logger jest automatycznie opakowywany przez wewnętrzny wrapper, który filtruje logi według `logLevel`:
505
-
506
- ```typescript
507
- // Prosty logger - używa console
508
141
  const config = new CommandBusConfig({
509
142
  redisUrl: 'redis://localhost:6379',
510
143
  logger: console,
511
- logLevel: 'log', // debug | log | warn | error
512
144
  });
513
145
 
514
- // Własny logger - musi implementować metody: log, error, warn, debug
515
- class MyLogger {
516
- log(message: string, ...args: unknown[]): void {
517
- // Twoja implementacja
518
- }
519
- error(message: string, ...args: unknown[]): void {
520
- // Twoja implementacja
521
- }
522
- warn(message: string, ...args: unknown[]): void {
523
- // Twoja implementacja
524
- }
525
- debug(message: string, ...args: unknown[]): void {
526
- // Twoja implementacja
527
- }
528
- }
529
-
530
- const config2 = new CommandBusConfig({
531
- redisUrl: 'redis://localhost:6379',
532
- logger: new MyLogger(),
533
- logLevel: 'debug', // Wszystkie poziomy
534
- });
535
- ```
536
-
537
- ## API Reference
538
-
539
- ### CommandBus
540
-
541
- #### `dispatch(command: Command): Promise<void>`
542
-
543
- Wysyła komendę do kolejki bez oczekiwania na wynik (fire-and-forget).
544
-
545
- **Flow**:
546
- 1. Kompresja payloadu (jeśli >threshold)
547
- 2. Pobranie/utworzenie kolejki z cache
548
- 3. Dodanie job do BullMQ
549
- 4. Natychmiastowy return
550
-
551
- ```typescript
552
- await commandBus.dispatch(new CreateUserCommand('email@example.com', 'Jan Kowalski'));
146
+ const bus = new CommandBus(config);
553
147
  ```
554
148
 
555
- #### `call<T>(command: Command, timeout?: number): Promise<T>`
556
-
557
- Wywołuje komendę synchronicznie i czeka na odpowiedź przez Redis Pub/Sub (RPC). Domyślny timeout: 30000ms (30s).
558
-
559
- **Flow**:
560
- 1. Rejestracja pending call z timeoutem
561
- 2. Kompresja payloadu (jeśli >threshold)
562
- 3. Przygotowanie metadanych RPC (correlationId, responseChannel)
563
- 4. Dodanie job do BullMQ
564
- 5. Oczekiwanie na odpowiedź przez shared subscriber
565
- 6. Dekompresja odpowiedzi
566
- 7. Zwrócenie wyniku lub błędu
149
+ ### Rejestracja handlera (worker)
567
150
 
568
151
  ```typescript
569
- const result = await commandBus.call<{ userId: string }>(command, 5000);
570
- ```
571
-
572
- #### `handle<T>(commandClass, handler): void`
573
-
574
- Rejestruje handler dla określonej klasy komendy. Tylko jeden handler per typ komendy.
575
-
576
- **Automatyczne akcje**:
577
- 1. Rejestracja handlera w Map
578
- 2. Utworzenie workera BullMQ
579
- 3. Uruchomienie benchmarku dla optymalnego concurrency
580
- 4. Utworzenie metrics collector
581
- 5. Setup event handlers
582
-
583
- ```typescript
584
- commandBus.handle(CreateUserCommand, async (command) => {
585
- const user = await createUser(command.email, command.name);
586
- return { userId: user.id };
152
+ bus.handle(CreateUserCommand, async (command) => {
153
+ const { name, email } = command.__payload;
154
+ // Logika biznesowa
155
+ await userService.create({ name, email });
156
+ return { userId: '123' };
587
157
  });
588
158
  ```
589
159
 
590
- #### `close(): Promise<void>`
591
-
592
- Zamyka wszystkie połączenia i workery z graceful shutdown.
593
-
594
- **Cleanup**:
595
- 1. Zamknięcie wszystkich workerów BullMQ
596
- 2. Zamknięcie wszystkich kolejek z cache
597
- 3. Zamknięcie RpcCoordinator (reject pending calls)
598
- 4. Zamknięcie 3 połączeń Redis (Queue, Worker, RPC)
599
-
600
- ```typescript
601
- await commandBus.close();
602
- ```
603
-
604
- ### Command
605
-
606
- Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command`:
160
+ ### Wysyłanie komendy (fire-and-forget)
607
161
 
608
162
  ```typescript
609
- class MyCommand extends Command {
610
- constructor(public readonly data: string) {
611
- super();
612
- }
613
- }
163
+ const cmd = new CreateUserCommand({ name: 'Jan Kowalski', email: 'jan@example.com' });
164
+ await bus.dispatch(cmd);
614
165
  ```
615
166
 
616
- **Właściwości automatyczne**:
617
- - `__id` - unikalny UUID (randomUUID)
618
- - `__name` - nazwa klasy komendy (constructor.name)
619
- - `__time` - timestamp utworzenia (Date.now())
620
-
621
- **Metody statyczne**:
622
- - `reconstructDates(obj)` - rekonstrukcja obiektów Date z serializowanych danych
623
-
624
- ### CommandBusConfig
625
-
626
- Konfiguracja CommandBus z opcjami:
167
+ ### Wysyłanie batch
627
168
 
628
169
  ```typescript
629
- interface CommandBusConfigOptions {
630
- redisUrl?: string; // URL Redis (domyślnie: 'redis://localhost:6379' lub REDIS_URL)
631
- logger?: ILogger; // Logger (domyślnie console)
632
- logLevel?: 'debug' | 'log' | 'warn' | 'error'; // Poziom logowania (domyślnie 'log')
633
- concurrency?: number; // Liczba workerów (domyślnie 1 lub auto)
634
- maxAttempts?: number; // Maksymalna liczba prób (domyślnie 1)
635
- backoffDelay?: number; // Opóźnienie między próbami w ms (domyślnie 2000)
636
- queueMode?: 'fifo' | 'lifo'; // Tryb kolejki (domyślnie 'fifo')
637
- commandLog?: string; // Ścieżka do katalogu logów komend (opcjonalnie)
638
- autoOptimize?: boolean; // Auto-optymalizacja concurrency (domyślnie true)
639
- compressionThreshold?: number; // Próg kompresji w bajtach (domyślnie 1024)
640
- }
170
+ const commands = [
171
+ new CreateUserCommand({ name: 'Anna Nowak', email: 'anna@example.com' }),
172
+ new CreateUserCommand({ name: 'Piotr Wiśniewski', email: 'piotr@example.com' }),
173
+ ];
174
+ await bus.dispatchBatch(commands);
641
175
  ```
642
176
 
643
- ## Przykłady Użycia
644
-
645
- ### Podstawowy przykład z RPC
177
+ ### Wywołanie RPC (request/response)
646
178
 
647
179
  ```typescript
648
- import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
649
-
650
- // Definicja komendy
651
- class CalculateCommand extends Command {
652
- constructor(
653
- public readonly a: number,
654
- public readonly b: number,
655
- public readonly operation: 'add' | 'multiply',
656
- ) {
657
- super();
658
- }
659
- }
660
-
661
- // Konfiguracja
662
- const config = new CommandBusConfig({
663
- redisUrl: 'redis://localhost:6379',
664
- });
665
- const commandBus = new CommandBus(config);
666
-
667
- // Rejestracja handlera
668
- commandBus.handle(CalculateCommand, async (command) => {
669
- console.log(`Calculating: ${command.a} ${command.operation} ${command.b}`);
670
-
671
- switch (command.operation) {
672
- case 'add':
673
- return command.a + command.b;
674
- case 'multiply':
675
- return command.a * command.b;
676
- }
677
- });
678
-
679
- // Fire-and-forget
680
- await commandBus.dispatch(new CalculateCommand(5, 3, 'add'));
681
-
682
- // RPC - czekamy na wynik
683
- const result = await commandBus.call<number>(
684
- new CalculateCommand(5, 3, 'multiply'),
685
- 5000 // timeout 5s
686
- );
687
- console.log(`Result: ${result}`); // Result: 15
180
+ const result = await bus.call<{ userId: string }>(cmd, 5000); // timeout 5s
181
+ console.log(result.userId); // '123'
688
182
  ```
689
183
 
690
- ### Równoległe wywołania RPC
184
+ ### Zamykanie
691
185
 
692
186
  ```typescript
693
- // Wiele równoległych RPC calls
694
- const [result1, result2, result3] = await Promise.all([
695
- commandBus.call<number>(new CalculateCommand(10, 5, 'add')),
696
- commandBus.call<UserInfo>(new GetUserInfoCommand('user-1')),
697
- commandBus.call<ValidationResult>(new ValidateUserCommand('jan@example.com', 30)),
698
- ]);
699
-
700
- console.log('All results:', { result1, result2, result3 });
187
+ await bus.close(); // Graceful shutdown czeka na aktywne zadania
701
188
  ```
702
189
 
703
- ### Obsługa błędów
190
+ ## API
704
191
 
705
- ```typescript
706
- commandBus.handle(CreateUserCommand, async (command) => {
707
- try {
708
- // Walidacja
709
- if (!command.email.includes('@')) {
710
- throw new Error('Invalid email format');
711
- }
712
-
713
- const user = await createUser(command.email, command.name);
714
- return { userId: user.id };
715
- } catch (error) {
716
- // Loguj błąd
717
- console.error('Failed to create user:', error);
718
-
719
- // Rzuć błąd - BullMQ spróbuje ponownie (maxAttempts)
720
- throw error;
721
- }
722
- });
192
+ ### `CommandBus`
723
193
 
724
- // Obsługa błędów w RPC
725
- try {
726
- const result = await commandBus.call(new CreateUserCommand('invalid-email', 'Jan'));
727
- } catch (error) {
728
- console.error('RPC failed:', error.message);
729
- }
730
- ```
194
+ | Metoda | Opis |
195
+ |--------|------|
196
+ | `dispatch(command)` | Wysyła komendę fire-and-forget (`XADD`) |
197
+ | `dispatchBatch(commands)` | Wysyła wiele komend w jednym pipeline |
198
+ | `call<T>(command, timeout?)` | RPC wysyła i czeka na wynik (`XADD` + `BRPOP`). Timeout per wywołanie w ms (domyślnie `30000`) |
199
+ | `handle(CommandClass, handler)` | Rejestruje handler dla komendy |
200
+ | `close()` | Graceful shutdown — zamyka połączenia i czeka na aktywne zadania |
731
201
 
732
- ### Graceful Shutdown
202
+ ### `Command<T>`
733
203
 
734
- ```typescript
735
- process.on('SIGTERM', async () => {
736
- console.log('Shutting down CommandBus...');
737
- await commandBus.close();
738
- process.exit(0);
739
- });
204
+ Bazowa klasa abstrakcyjna dla komend:
740
205
 
741
- process.on('SIGINT', async () => {
742
- console.log('Shutting down CommandBus...');
743
- await commandBus.close();
744
- process.exit(0);
745
- });
746
- ```
206
+ | Pole | Typ | Opis |
207
+ |------|-----|------|
208
+ | `__name` | `string` | Nazwa komendy (nazwa klasy) |
209
+ | `__id` | `string` | UUID v4 |
210
+ | `__time` | `number` | Timestamp utworzenia (ms) |
211
+ | `__payload` | `T` | Dane biznesowe komendy |
747
212
 
748
- ## Zaawansowane Funkcje
213
+ ## Konfiguracja
749
214
 
750
- ### 1. Dynamiczne Concurrency
215
+ ### Zmienne środowiskowe
751
216
 
752
- WorkerOrchestrator automatycznie dostosowuje concurrency na podstawie metryk:
217
+ | Zmienna | Domyślna | Opis |
218
+ |---------|----------|------|
219
+ | `REDIS_URL` | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB |
220
+ | `REDIS_RETRY_DELAY` | `5000` | Opóźnienie między próbami reconnect (ms) |
221
+ | `REDIS_MAX_RETRIES` | `0` | Maks. prób reconnect do Redis (`0` = nieskończoność) |
222
+ | `LOG_LEVEL` | `log` | Poziom logowania (`debug`, `log`, `warn`, `error`) |
223
+ | `COMMAND_BUS_CONCURRENCY` | `60 × CPU` | Maks. równoległych wiadomości per konsument (I/O-bound: default, CPU-bound: `availableParallelism()`) |
224
+ | `COMMAND_BUS_MAX_ATTEMPTS` | `3` | Maks. prób przetworzenia wiadomości |
225
+ | `COMMAND_BUS_LOG` | _(puste)_ | Ścieżka do katalogu logów komend |
226
+ | `COMMAND_BUS_POOL_SIZE` | `2 × CPU` | Rozmiar puli połączeń Redis |
227
+ | `COMMAND_BUS_MAX_CONCURRENT_RPC` | `50` | Maks. równoległych wywołań RPC |
228
+ | `COMMAND_BUS_BATCH_SIZE` | `100` | Wiadomości pobieranych w jednym `XREADGROUP` (mniej roundtripów = wyższy throughput) |
229
+ | `COMMAND_BUS_CLAIM_TIMEOUT` | `30000` | Czas (ms) po którym stalled wiadomość jest przejmowana |
230
+ | `COMMAND_BUS_MAX_RETAINED` | `10000` | Maks. wiadomości w strumieniu (`XTRIM ~`) |
753
231
 
754
- - **Benchmark przy starcie** - optymalny concurrency dla każdego workera
755
- - **Event-driven metrics** - zbieranie metryk z workerów
756
- - **Dynamiczne dostosowanie** - +/-20% co 30s (cooldown)
757
- - **Limity** - min 10, max 2000
232
+ ### Konfiguracja programowa
758
233
 
759
234
  ```typescript
760
- // Automatyczne - benchmark ustali optymalną wartość
761
235
  const config = new CommandBusConfig({
762
236
  redisUrl: 'redis://localhost:6379',
763
- // concurrency zostanie ustalone przez benchmark (np. 15-20)
764
- });
765
- ```
766
-
767
- ### 2. Process Isolation w RPC
768
-
769
- Każdy proces Node.js ma unikalny UUID - odpowiedzi RPC są izolowane między procesami:
770
-
771
- ```
772
- Process A (UUID: abc-123):
773
- - Kanał: rpc:response:abc-123:*
774
- - Otrzymuje tylko swoje odpowiedzi
775
-
776
- Process B (UUID: def-456):
777
- - Kanał: rpc:response:def-456:*
778
- - Otrzymuje tylko swoje odpowiedzi
779
- ```
780
-
781
- ### 3. Shared Subscriber Pattern
782
-
783
- Jeden shared subscriber dla wszystkich RPC calls zamiast N subskrybentów:
784
-
785
- **Tradycyjne podejście** (N subskrybentów):
786
- ```
787
- 1000 RPC calls → 1000 Redis subscriptions → duże obciążenie
788
- ```
789
-
790
- **Shared Subscriber** (1 subskrybent):
791
- ```
792
- 1000 RPC calls → 1 Redis pattern subscription → multiplexing w pamięci
793
- ```
794
-
795
- ### 4. Automatyczna Kompresja
796
-
797
- PayloadCompressionService automatycznie kompresuje duże payloady:
798
-
799
- ```typescript
800
- // Mała komenda (<1KB) - brak kompresji
801
- const smallCommand = new CreateUserCommand('jan@example.com', 'Jan');
802
- await commandBus.call(smallCommand); // Bez kompresji
803
-
804
- // Duża komenda (>1KB) - automatyczna kompresja
805
- const largeCommand = new ProcessReportCommand(largeData); // 5KB
806
- await commandBus.call(largeCommand); // Automatyczna kompresja gzip → base64
807
- ```
808
-
809
- **Flagi kompresji**:
810
- - `__compressed: true` - payload został skompresowany
811
- - Automatyczna dekompresja w JobProcessor
812
- - Transparent dla użytkownika
813
-
814
- ### 5. Command Logging
815
-
816
- Persystencja wszystkich komend do plików JSONL:
817
-
818
- ```typescript
819
- const config = new CommandBusConfig({
820
- commandLog: './command-logs',
821
- });
822
-
823
- // Każda komenda jest logowana do pliku:
824
- // ./command-logs/2025-01-27T10.jsonl
825
- ```
826
-
827
- **Use cases**:
828
- - Auditing i compliance
829
- - Replay komend
830
- - Debugging produkcyjnych problemów
831
- - Analiza przepływu komend
832
-
833
- ### 6. RPC Job Cancellation
834
-
835
- Automatyczne usuwanie niepodjętych jobów RPC przy timeout:
836
-
837
- ```
838
- RPC Timeout Flow:
839
-
840
- User Code RpcCoordinator Redis JobProcessor
841
- │ │ │ │
842
- │── call(cmd) ─────▶│ │ │
843
- │ │── registerCall ──▶│ │
844
- │ │── queue.add ─────▶│ │
845
- │ │ │ │
846
- │ [timeout] │ │ │
847
- │ │ │ │
848
- │ │── markCancelled ─▶│ SET rpc:cancelled:xxx
849
- │ │── tryRemoveJob ──▶│ (próba usunięcia)
850
- │◀── Error ─────────│ │ │
851
- │ │ │ │
852
- │ │ │ [job picked up] │
853
- │ │ │──────────────────▶│
854
- │ │ │ │── isCancelled?
855
- │ │ │◀──────────────────│ → true
856
- │ │ │ │── SKIP handler
857
- │ │ │ │── clearCancellation
858
- ```
859
-
860
- **Kluczowe cechy**:
861
- - **Dwufazowe anulowanie**: Flaga Redis + próba usunięcia joba z kolejki
862
- - **Graceful degradation**: Błędy Redis nie blokują przetwarzania
863
- - **TTL 24h**: Automatyczne wygaśnięcie kluczy cancellation
864
- - **Aktywne czyszczenie**: Klucze usuwane po przetworzeniu lub usunięciu joba
865
- - **Kompatybilność wsteczna**: Funkcjonalność jest opcjonalna
866
-
867
- **Korzyści**:
868
- - Oszczędność zasobów - nieprzetworzone joby nie obciążają workerów
869
- - Brak efektów ubocznych - handler nie wykonuje się dla timeout'owanych RPC
870
- - Lepsza diagnostyka - logi pokazują pominięte joby
871
-
872
- ## Best Practices
873
-
874
- ### 1. Używaj TypeScript
875
-
876
- Biblioteka jest napisana w TypeScript z strict mode. Wykorzystaj typy dla lepszego DX:
877
-
878
- ```typescript
879
- interface UserCreatedResult {
880
- userId: string;
881
- createdAt: Date;
882
- }
883
-
884
- const result = await commandBus.call<UserCreatedResult>(command);
885
- ```
886
-
887
- ### 2. Idempotentne handlery
888
-
889
- Handlery powinny być idempotentne (wielokrotne wykonanie = ten sam rezultat):
890
-
891
- ```typescript
892
- commandBus.handle(CreateUserCommand, async (command) => {
893
- // Sprawdź czy użytkownik już istnieje
894
- const existing = await findUserByEmail(command.email);
895
- if (existing) {
896
- return { userId: existing.id }; // Zwróć istniejącego
897
- }
898
-
899
- // Utwórz nowego
900
- const user = await createUser(command.email, command.name);
901
- return { userId: user.id };
237
+ logger: console,
238
+ logLevel: 'log', // debug | log | warn | error
239
+ redisRetryDelay: 5000, // Opóźnienie reconnect (ms)
240
+ redisMaxRetries: 0, // 0 = nieskończoność
241
+ concurrency: 960, // I/O-bound: 60 × CPU, CPU-bound: availableParallelism()
242
+ poolSize: 32, // Rozmiar puli połączeń Redis
243
+ maxConcurrentRpc: 100, // Maks. równoległych BRPOP
244
+ batchSize: 100, // Wiadomości per XREADGROUP
245
+ claimTimeout: 60000, // Stalled message timeout (ms)
246
+ maxRetained: 50000, // XTRIM ~ limit
247
+ maxAttempts: 5, // Maks. prób przetworzenia
902
248
  });
903
249
  ```
904
250
 
905
- ### 3. Monitorowanie RPC timeoutów
906
-
907
- ```typescript
908
- // Ustaw timeout dostosowany do czasu przetwarzania komendy
909
- const shortCommand = new QuickCommand();
910
- const result1 = await commandBus.call(shortCommand, 1000); // 1s dla szybkich operacji
911
-
912
- const longRunningCommand = new ProcessReportCommand();
913
- const result2 = await commandBus.call(longRunningCommand, 60000); // 60s dla długich operacji
914
- ```
915
-
916
- ### 4. Walidacja w handlerach
251
+ ## Flow danych
252
+
253
+ ### 1. Dispatch (fire-and-forget)
254
+
255
+ ```
256
+ Producer Redis Worker
257
+ │ │ │
258
+ │ 1. serialize(command) │ │
259
+ │ → MessagePack encode │ │
260
+ │ │ │
261
+ │ 2. RedisCodec.encode(buffer) │ │
262
+ │ → base64 string │ │
263
+ │ │ │
264
+ │ 3. XADD cmd:CreateUser * ──→ │ stream: cmd:CreateUser │
265
+ │ data <base64> │ ┌─────────────────────┐ │
266
+ │ │ │ msg-1: data=<b64> │ │
267
+ │ │ └─────────────────────┘ │
268
+ │ │ │
269
+ │ │ ←── 4. XREADGROUP GROUP │
270
+ │ │ workers consumer-1 │
271
+ │ │ COUNT 100 BLOCK 5000 │
272
+ │ │ STREAMS cmd:CreateUser > │
273
+ │ │ │
274
+ │ │ ──→ [msg-1, [data, <b64>]] │
275
+ │ │ │
276
+ │ │ 5. RedisCodec.decode │
277
+ │ │ 6. deserialize(buffer) │
278
+ │ │ 7. handler(command) │
279
+ │ │ │
280
+ │ │ ←── 8. XACK cmd:CreateUser │
281
+ │ │ workers msg-1 │
282
+ │ │ │
283
+ │ │ ←── 9. XTRIM cmd:CreateUser │
284
+ │ │ MAXLEN ~ 10000 │
285
+ │ │ (co batchSize wiadomości)│
286
+ ```
287
+
288
+ ### 2. RPC (request/response)
289
+
290
+ ```
291
+ Caller Redis Worker
292
+ │ │ │
293
+ │ 1. serialize(command) │ │
294
+ │ 2. Wrap w RpcEnvelope: │ │
295
+ │ { commandData, rpc: { │ │
296
+ │ correlationId, │ │
297
+ │ responseKey │ │
298
+ │ }} │ │
299
+ │ │ │
300
+ │ 3. XADD cmd:GetUser * ────→ │ stream: cmd:GetUser │
301
+ │ data <envelope> │ ┌─────────────────────┐ │
302
+ │ rpc 1 │ │ msg-1: data=<env> │ │
303
+ │ │ │ rpc=1 │ │
304
+ │ 4. BRPOP rpc:res:{uuid} ──→ │ └─────────────────────┘ │
305
+ │ timeout 30s │ │
306
+ │ (dedykowane połączenie │ ←── 5. XREADGROUP │
307
+ │ z RpcConnectionPool) │ │
308
+ │ │ 6. Wykryj marker rpc=1 │
309
+ │ │ 7. Rozpakuj envelope │
310
+ │ │ 8. handler(commandData) │
311
+ │ │ 9. serialize({result}) │
312
+ │ │ │
313
+ │ │ ←── 10. LPUSH rpc:res:{uuid}│
314
+ │ │ <result> │
315
+ │ │ EXPIRE rpc:res:{uuid} 60│
316
+ │ │ │
317
+ │ ←── result z BRPOP │ │
318
+ │ 11. deserialize(result) │ │
319
+ │ 12. DEL rpc:res:{uuid} │ │
320
+ ```
321
+
322
+ ### 3. Recovery + Dead Letter Queue
323
+
324
+ ```
325
+ PendingRecovery Redis
326
+ │ │
327
+ │ (setInterval co claimTimeout) │
328
+ │ │
329
+ │ 1. XPENDING cmd:CreateUser ─→│ Pending entries:
330
+ │ workers - + 10 │ [msg-5, consumer-1,
331
+ │ │ idle: 45000, delivery: 2]
332
+ │ │
333
+ │ if idle > claimTimeout │
334
+ │ AND delivery < maxAttempts: │
335
+ │ │
336
+ │ 2. XCLAIM cmd:CreateUser ──→ │ Przejęcie wiadomości
337
+ │ workers consumer-2 │
338
+ │ 30000 msg-5 │
339
+ │ │
340
+ │ 3. processMessage(msg-5) │ → handler() → XACK
341
+ │ │
342
+ │ if delivery >= maxAttempts: │
343
+ │ │
344
+ │ 4. XCLAIM msg-5 ───────────→ │
345
+ │ 5. XADD dlq:cmd:CreateUser → │ Dead Letter Queue
346
+ │ * ...fields │ ┌──────────────────────┐
347
+ │ original_stream │ │ msg: data + metadata │
348
+ │ cmd:CreateUser │ │ original_stream │
349
+ │ delivery_count 3 │ │ delivery_count: 3 │
350
+ │ 6. XACK cmd:CreateUser ────→ │ └──────────────────────┘
351
+ │ workers msg-5 │
352
+ ```
353
+
354
+ ## Komponenty
355
+
356
+ ### RedisStreamsTransport (fasada)
357
+
358
+ Kompozycja 4 komponentów implementująca `ITransport`:
359
+
360
+ | Komponent | Odpowiedzialność | Komendy Redis |
361
+ |-----------|------------------|---------------|
362
+ | **StreamProducer** | Enqueue / enqueueBatch | `XADD`, pipeline |
363
+ | **StreamConsumer** | Cykl życia konsumenta, deduplicacja | `XGROUP CREATE` |
364
+ | **RpcHandler** | Request/response RPC | `XADD`, `BRPOP`, `LPUSH`, `DEL` |
365
+ | **PendingRecovery** | Automatyczny retry + DLQ | `XPENDING`, `XCLAIM`, `XACK` |
366
+
367
+ ### StreamConsumer (koordynator)
368
+
369
+ StreamConsumer komponuje dwa podkomponenty:
370
+
371
+ | Podkomponent | Odpowiedzialność | LOC |
372
+ |--------------|------------------|-----|
373
+ | **MessageProcessor** | Parsowanie fields, walidacja, RPC detection, handler, XACK/XTRIM | ~147 |
374
+ | **ConsumerLoop** | while(running) loop, XREADGROUP BLOCK, concurrency limiter, backoff | ~120 |
375
+ | **StreamConsumer** | Koordynacja: compose, deduplicacja (Map z TTL sweep), lifecycle | ~188 |
376
+
377
+ ### Pule połączeń
378
+
379
+ | Pula | Wzorzec | Tworzenie | Przeznaczenie |
380
+ |------|---------|-----------|---------------|
381
+ | **RedisConnectionPool** | Round-robin, eager | Przy starcie (`size` połączeń) | Operacje nieblokujące: XADD, XACK, DEL, pipeline |
382
+ | **RpcConnectionPool** | Bounded, lazy | Przy `acquire()` (max `maxSize`) | Operacje blokujące: BRPOP |
917
383
 
918
- ```typescript
919
- commandBus.handle(CreateUserCommand, async (command) => {
920
- // Walidacja na początku
921
- if (!command.email || !command.email.includes('@')) {
922
- throw new Error('Invalid email');
923
- }
384
+ **RedisConnectionPool** dodatkowo udostępnia `createDedicated()` — tworzy izolowane połączenie poza pulą, używane przez `ConsumerLoop` (XREADGROUP BLOCK blokuje socket).
924
385
 
925
- if (!command.name || command.name.length < 2) {
926
- throw new Error('Invalid name');
927
- }
386
+ ### Serializacja
928
387
 
929
- // Logika biznesowa
930
- return await createUser(command.email, command.name);
931
- });
388
+ | Komponent | Warstwa | Opis |
389
+ |-----------|---------|------|
390
+ | **MsgpackSerializer** | Aplikacja | MessagePack z Date extension type — 1 krok zamiast 7 |
391
+ | **RedisCodec** | Transport | Base64 encode/decode — chroni binarne dane przed korupcją UTF-8 przez ioredis |
392
+
393
+ ### Concurrency Limiter (ConsumerLoop)
394
+
395
+ ```
396
+ ┌─ slot 1: handler(msg-1) ──→ done → slot wolny
397
+ XREADGROUP ────→├─ slot 2: handler(msg-2) ──→ done → slot wolny
398
+ (COUNT=N) └─ slot 3: handler(msg-3) ──→ (trwa...)
399
+
400
+ czekam (Promise.race) ←────┘
401
+ aż zwolni się slot
932
402
  ```
933
403
 
934
- ### 5. Logowanie kontekstu
404
+ - `concurrency` kontroluje liczbę slotów (równoległych handlerów)
405
+ - `batchSize` kontroluje ile wiadomości czytanych na raz
406
+ - Dostępne sloty = `concurrency - active.size`
407
+ - Nowy batch = `Math.min(batchSize, availableSlots)`
935
408
 
936
- ```typescript
937
- commandBus.handle(CreateUserCommand, async (command) => {
938
- console.log('Processing CreateUserCommand', {
939
- commandId: command.__id,
940
- timestamp: command.__time,
941
- email: command.email,
942
- });
409
+ ### Deduplicacja (StreamConsumer)
943
410
 
944
- // Logika biznesowa
945
- const user = await createUser(command.email, command.name);
411
+ `Map<string, number>` — messageId → timestamp:
946
412
 
947
- console.log('User created successfully', {
948
- commandId: command.__id,
949
- userId: user.id,
950
- });
413
+ - Przed przetworzeniem: `sweepStaleProcessing()` usuwa wpisy starsze niż `staleThreshold` (5 min)
414
+ - Duplikat: `processing.has(messageId) → return` (skip)
415
+ - Po przetworzeniu: `processing.delete(messageId)`
951
416
 
952
- return { userId: user.id };
953
- });
954
- ```
417
+ Chroni przed podwójnym przetworzeniem gdy `PendingRecovery` przejmuje wiadomość która jest jeszcze aktywna w consumer loop.
955
418
 
956
- ## Testing
419
+ ### XTRIM (MessageProcessor)
957
420
 
958
- ### Unit testy
421
+ Throttled per-stream: counter wiadomości na strumień, co `batchSize` wiadomości pipeline `XACK + XTRIM ~ maxRetained`.
959
422
 
960
- ```typescript
961
- import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
962
-
963
- describe('User Commands', () => {
964
- let commandBus: CommandBus;
965
-
966
- beforeAll(async () => {
967
- const config = new CommandBusConfig({
968
- redisUrl: 'redis://localhost:6379',
969
- logger: console,
970
- logLevel: 'error', // Tylko błędy w testach
971
- });
972
-
973
- commandBus = new CommandBus(config);
974
-
975
- // Zarejestruj handler
976
- commandBus.handle(CreateUserCommand, async (command) => {
977
- return { userId: 'test-user-id' };
978
- });
979
- });
980
-
981
- afterAll(async () => {
982
- await commandBus.close();
983
- });
984
-
985
- it('should create user', async () => {
986
- const command = new CreateUserCommand('test@example.com', 'Test User');
987
- const result = await commandBus.call<{ userId: string }>(command, 5000);
988
-
989
- expect(result.userId).toBeDefined();
990
- expect(result.userId).toBe('test-user-id');
991
- });
992
-
993
- it('should handle multiple commands in parallel', async () => {
994
- const commands = [
995
- new CreateUserCommand('user1@example.com', 'User 1'),
996
- new CreateUserCommand('user2@example.com', 'User 2'),
997
- new CreateUserCommand('user3@example.com', 'User 3'),
998
- ];
999
-
1000
- const results = await Promise.all(
1001
- commands.map((cmd) => commandBus.call<{ userId: string }>(cmd, 5000))
1002
- );
1003
-
1004
- expect(results).toHaveLength(3);
1005
- results.forEach((result) => {
1006
- expect(result.userId).toBeDefined();
1007
- });
1008
- });
1009
- });
1010
423
  ```
1011
-
1012
- ## Troubleshooting
1013
-
1014
- ### Connection refused (Redis)
1015
-
1016
- Upewnij się że Redis działa:
1017
-
1018
- ```bash
1019
- redis-cli ping
1020
- # Powinno zwrócić: PONG
424
+ Strumień A: [1] [2] [3] ... [10] → pipeline XACK + XTRIM → reset counter
425
+ Strumień B: [1] [2] [3] → zwykły XACK (nie osiągnął batchSize)
1021
426
  ```
1022
427
 
1023
- ### Timeout na RPC call
1024
-
1025
- Zwiększ timeout lub sprawdź czy handler został zarejestrowany:
1026
-
1027
- ```typescript
1028
- // Zwiększ timeout
1029
- const result = await commandBus.call(command, 60000); // 60s
1030
-
1031
- // Sprawdź czy handler został zarejestrowany PRZED wywołaniem
1032
- commandBus.handle(MyCommand, async (command) => {
1033
- // Handler implementation
1034
- return { result: 'success' };
1035
- });
1036
-
1037
- // Teraz możesz wywołać komendę
1038
- const result = await commandBus.call(new MyCommand());
1039
- ```
1040
-
1041
- ### Handler nie został wywołany
1042
-
1043
- Upewnij się że handler został zarejestrowany przed wysłaniem komendy:
1044
-
1045
- ```typescript
1046
- // ❌ ŹLE - handler po dispatch
1047
- await commandBus.dispatch(new MyCommand());
1048
- commandBus.handle(MyCommand, async (cmd) => { ... }); // Za późno!
428
+ ### Graceful Shutdown
1049
429
 
1050
- // ✅ DOBRZE - handler przed dispatch
1051
- commandBus.handle(MyCommand, async (cmd) => { ... });
1052
- await commandBus.dispatch(new MyCommand()); // Teraz OK
1053
430
  ```
1054
-
1055
- ### Wysokie zużycie pamięci
1056
-
1057
- 1. **Wyłącz command logging** jeśli nie jest potrzebne
1058
- 2. **Zmniejsz concurrency** jeśli workery używają dużo pamięci
1059
- 3. **Zwiększ compressionThreshold** jeśli duże payloady powodują problemy
1060
-
1061
- ```typescript
1062
- const config = new CommandBusConfig({
1063
- commandLog: undefined, // Wyłącz logging
1064
- concurrency: 5, // Zmniejsz concurrency
1065
- compressionThreshold: 512, // Kompresuj już od 512B
1066
- });
431
+ close():
432
+ 1. consumer.running = false → propaguje do wszystkich ConsumerLoop
433
+ 2. recovery.stop() → clearInterval
434
+ 3. conn.disconnect() → przerywa XREADGROUP BLOCK
435
+ 4. await consumerLoopPromises → czeka na aktywne handlery
436
+ 5. pool.close() → quit() na connection pool
437
+ 6. rpcPool.close() → quit() available, disconnect() borrowed
1067
438
  ```
1068
439
 
1069
- ### Worker stalled
1070
-
1071
- Worker został zatrzymany (prawdopodobnie crashed). BullMQ automatycznie przeniesie job do innego workera.
1072
-
1073
- **Przyczyny**:
1074
- - Out of memory
1075
- - Uncaught exception w handlerze
1076
- - Timeout w handlerze
1077
-
1078
- **Rozwiązanie**:
1079
- - Dodaj try/catch w handlerze
1080
- - Zwiększ pamięć dla procesu
1081
- - Zmniejsz concurrency
1082
-
1083
- ## Struktura Projektu
440
+ ## Struktura projektu
1084
441
 
1085
442
  ```
1086
443
  src/
1087
- ├── command-bus/ # Główna logika CommandBus
1088
- ├── config/ # Konfiguracja CommandBus
1089
- ├── command-bus-config.ts
1090
- │ └── auto-config-optimizer.ts
1091
- │ ├── job/ # Przetwarzanie jobów i opcje
1092
- │ │ ├── job-processor.ts
1093
- │ └── job-options-builder.ts
1094
- │ ├── logging/ # Command logging do plików
1095
- │ │ └── command-logger.ts
1096
- │ ├── queue/ # Queue management i cache
1097
- │ │ └── queue-manager.ts
1098
- │ ├── rpc/ # RPC coordination
1099
- │ │ ├── rpc-coordinator.ts
1100
- │ │ ├── payload-compression.service.ts
1101
- │ │ └── rpc-job-cancellation.service.ts # Anulowanie jobów RPC przy timeout
1102
- ├── worker/ # Worker orchestration
1103
- ├── worker-orchestrator.ts
1104
- │ │ ├── worker-benchmark.ts
1105
- │ │ └── worker-metrics-collector.ts
1106
- │ ├── types/ # Typy TypeScript
1107
- │ │ └── index.ts
1108
- ├── command.ts # Klasa bazowa Command
1109
- └── index.ts # CommandBus główna klasa
1110
- ├── shared/ # Wspólne komponenty
1111
- │ ├── config/ # Base config z Redis
1112
- │ └── base-config.ts
1113
- │ ├── logging/ # Logger wrapper z poziomami
1114
- │ │ ├── logger.ts
1115
- │ │ └── log-level.ts
1116
- │ ├── redis/ # Fabryka połączeń Redis (SRP)
1117
- │ │ ├── redis-connection-factory.ts # Tworzenie połączeń z event handlers
1118
- │ │ ├── redis-error-formatter.ts # Formatowanie błędów (AggregateError)
1119
- └── index.ts # Eksporty modułu
1120
- └── types.ts # Współdzielone typy
1121
- ├── examples/ # Przykłady użycia
1122
- ├── rpc.demo.ts # Demo RPC calls
1123
- │ ├── rpc-throughput.demo.ts # Demo wydajności RPC
1124
- │ ├── rpc-compression.demo.ts # Demo kompresji
1125
- │ └── rpc-resilience.demo.ts # Demo reconnect/failover (5 min test)
1126
- └── index.ts # Główny export pakietu
1127
- ```
1128
-
1129
- ## Migracja z pp-event-bus 1.x
1130
-
1131
- Jeśli używałeś Command Bus z pakietu `pp-event-bus` w wersji 1.x:
1132
-
1133
- ### Przed:
1134
-
1135
- ```typescript
1136
- import { CommandBus, Command, CommandBusConfig } from 'pp-event-bus';
1137
- ```
1138
-
1139
- ### Po:
444
+ ├── index.ts # Eksporty publiczne
445
+ ├── command-bus/
446
+ │ ├── index.ts # CommandBus (fasada wyższego poziomu)
447
+ ├── command.ts # Bazowa klasa Command<T>
448
+ │ ├── config/
449
+ │ │ └── command-bus-config.ts # Konfiguracja (env + params)
450
+ ├── transport/
451
+ ├── transport.interface.ts # Segregowane interfejsy ISP
452
+ │ │ ├── redis-streams-transport.ts # Fasada transportu (kompozycja 4 komponentów)
453
+ ├── stream-producer.ts # XADD enqueue / enqueueBatch
454
+ │ │ ├── stream-consumer.ts # Koordynator: lifecycle, dedup, compose
455
+ ├── message-processor.ts # Parse, validate, XACK/XTRIM, RPC detect
456
+ │ │ ├── consumer-loop.ts # XREADGROUP loop, concurrency limiter
457
+ │ │ ├── rpc-handler.ts # BRPOP/LPUSH request/response
458
+ │ │ ├── pending-recovery.ts # XPENDING/XCLAIM + Dead Letter Queue
459
+ │ └── redis-codec.ts # Base64 encode/decode
460
+ │ ├── serialization/
461
+ │ │ ├── serializer.interface.ts # ISerializer
462
+ │ │ └── msgpack-serializer.ts # MessagePack z Date extension
463
+ │ ├── logging/
464
+ │ │ └── command-logger.ts # Opcjonalny logger komend do plików
465
+ └── types/
466
+ └── index.ts # Typy (CommandPayload, CommandHandler)
467
+ ├── shared/
468
+ │ ├── types.ts # ILogger, TDict, TCallableAsync
469
+ ├── redis/
470
+ ├── connection-pool.ts # Round-robin eager pool
471
+ │ │ ├── rpc-connection-pool.ts # Bounded lazy pool (BRPOP)
472
+ │ │ └── redis-error-formatter.ts # Formatowanie błędów Redis
473
+ │ ├── logging/
474
+ │ │ ├── logger.ts # Logger implementacja
475
+ │ │ └── log-level.ts # Poziomy logowania
476
+ │ └── utils/
477
+ └── error-utils.ts # getErrorMessage()
478
+ └── examples/
479
+ ├── rpc.demo.ts # Demo RPC call
480
+ └── rpc-throughput.demo.ts # Demo throughput
481
+ ```
482
+
483
+ ## Testowanie
1140
484
 
1141
485
  ```bash
1142
- npm install pp-command-bus
1143
- ```
1144
-
1145
- ```typescript
1146
- import { CommandBus, Command, CommandBusConfig } from 'pp-command-bus';
1147
- ```
486
+ # Wszystkie testy
487
+ npm test
1148
488
 
1149
- **Pełna zgodność API** - jedyna zmiana to źródło importu.
489
+ # Z coverage
490
+ npm run test:coverage
1150
491
 
1151
- ## Wersjonowanie i Releases
1152
-
1153
- Projekt używa [Semantic Versioning](https://semver.org/lang/pl/) oraz automatycznych release'ów dzięki [semantic-release](https://github.com/semantic-release/semantic-release).
1154
-
1155
- ### Konwencja commitów
1156
-
1157
- Używamy [Conventional Commits](https://www.conventionalcommits.org/):
1158
-
1159
- ```bash
1160
- # Nowa funkcjonalność (minor release)
1161
- feat: dodano wsparcie dla delayed commands
492
+ # Lint
493
+ npm run lint
1162
494
 
1163
- # Poprawka błędu (patch release)
1164
- fix: naprawiono memory leak w RPC
1165
-
1166
- # Breaking change (major release)
1167
- feat!: zmieniono API CommandBusConfig
1168
-
1169
- # Inne typy (patch release)
1170
- docs: zaktualizowano dokumentację API
1171
- perf: zoptymalizowano przetwarzanie komend
1172
- refactor: uproszczono kod RPC handler
1173
- test: dodano testy dla command logging
495
+ # Format
496
+ npm run format:check
1174
497
  ```
1175
498
 
1176
- ### Typy commitów
1177
-
1178
- - `feat:` - nowa funkcjonalność (→ minor release)
1179
- - `fix:` - poprawka błędu (→ patch release)
1180
- - `perf:` - optymalizacja wydajności (→ patch release)
1181
- - `docs:` - zmiany w dokumentacji (→ patch release)
1182
- - `style:` - formatowanie kodu (→ patch release)
1183
- - `refactor:` - refaktoryzacja (→ patch release)
1184
- - `test:` - dodanie/poprawka testów (→ patch release)
1185
- - `build:` - zmiany w buildzie/zależnościach (→ patch release)
1186
- - `ci:` - zmiany w CI/CD (→ patch release)
1187
- - `chore:` - inne zmiany (bez release)
1188
- - `!` lub `BREAKING CHANGE:` - breaking change (→ major release)
1189
-
1190
- ## Dokumentacja
1191
-
1192
- ### Architektura
1193
-
1194
- Szczegółowa dokumentacja architektury systemu, wzorców projektowych i zasad SOLID:
1195
- - [ARCHITECTURE.md](src/command-bus/docs/ARCHITECTURE.md) - Kompletny opis architektury, diagramy komponentów, przepływy danych
1196
-
1197
- ### Komponenty
1198
-
1199
- - **QueueManager** - zarządzanie kolejkami BullMQ i cache kolejek
1200
- - **WorkerOrchestrator** - orkiestracja workerów, dynamiczne concurrency, benchmark
1201
- - **JobProcessor** - przetwarzanie jobów i wykonanie handlerów
1202
- - **RpcCoordinator** - zarządzanie wywołaniami RPC przez Redis Pub/Sub
1203
- - **RpcJobCancellationService** - anulowanie niepodjętych jobów RPC przy timeout
1204
- - **JobOptionsBuilder** - konfiguracja opcji dla jobów BullMQ
1205
- - **CommandLogger** - persystencja komend do plików JSONL
1206
- - **PayloadCompressionService** - automatyczna kompresja gzip
1207
- - **AutoConfigOptimizer** - optymalizacja concurrency na podstawie zasobów
1208
-
1209
- ## Licencja
499
+ ### Architektura testów
1210
500
 
1211
- MIT
501
+ | Typ | Pliki | Opis |
502
+ |-----|-------|------|
503
+ | Unit | `*.spec.ts` per komponent | Izolowane testy każdego komponentu |
504
+ | Integracja | `redis-streams-transport.spec.ts` | Testy fasady z mockami |
505
+ | Integracja | `command-bus.spec.ts` | Testy CommandBus end-to-end z mockami |
1212
506
 
1213
- ## Autorzy
1214
- - Mariusz Lejkowski <m.lejkowski@polskiepolisy.pl>
507
+ Pokrycie: **193 testów**, zero timing-dependent `setTimeout`, `jest.useFakeTimers()` dla backoff.
1215
508
 
1216
- ## Linki
509
+ ## Changelog
1217
510
 
1218
- - [GitLab Repository](https://gitlab.polskiepolisy.pl/lib/pp-command-bus)
1219
- - [Issue Tracker](https://gitlab.polskiepolisy.pl/lib/pp-command-bus/issues)
1220
- - [BullMQ Documentation](https://docs.bullmq.io/)
1221
- - [Semantic Versioning](https://semver.org/lang/pl/)
1222
- - [Conventional Commits](https://www.conventionalcommits.org/)
511
+ Pełny changelog dostępny w [CHANGELOG.md](CHANGELOG.md).