pp-command-bus 1.3.2 → 1.5.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 (60) hide show
  1. package/README.md +242 -72
  2. package/dist/command-bus/command-bus.spec.js +24 -29
  3. package/dist/command-bus/command-bus.spec.js.map +1 -1
  4. package/dist/command-bus/command.d.ts +22 -3
  5. package/dist/command-bus/command.js +17 -2
  6. package/dist/command-bus/command.js.map +1 -1
  7. package/dist/command-bus/index.d.ts +10 -0
  8. package/dist/command-bus/index.js +43 -5
  9. package/dist/command-bus/index.js.map +1 -1
  10. package/dist/command-bus/job/job-processor.d.ts +3 -1
  11. package/dist/command-bus/job/job-processor.js +17 -1
  12. package/dist/command-bus/job/job-processor.js.map +1 -1
  13. package/dist/command-bus/job/job-processor.spec.js +152 -12
  14. package/dist/command-bus/job/job-processor.spec.js.map +1 -1
  15. package/dist/command-bus/logging/command-logger.spec.js +13 -14
  16. package/dist/command-bus/logging/command-logger.spec.js.map +1 -1
  17. package/dist/command-bus/rpc/index.d.ts +3 -0
  18. package/dist/command-bus/rpc/index.js +4 -1
  19. package/dist/command-bus/rpc/index.js.map +1 -1
  20. package/dist/command-bus/rpc/payload-compression.service.d.ts +3 -4
  21. package/dist/command-bus/rpc/payload-compression.service.js +17 -20
  22. package/dist/command-bus/rpc/payload-compression.service.js.map +1 -1
  23. package/dist/command-bus/rpc/payload-compression.service.spec.js +20 -23
  24. package/dist/command-bus/rpc/payload-compression.service.spec.js.map +1 -1
  25. package/dist/command-bus/rpc/rpc-coordinator.d.ts +27 -1
  26. package/dist/command-bus/rpc/rpc-coordinator.js +113 -3
  27. package/dist/command-bus/rpc/rpc-coordinator.js.map +1 -1
  28. package/dist/command-bus/rpc/rpc-coordinator.spec.js +209 -5
  29. package/dist/command-bus/rpc/rpc-coordinator.spec.js.map +1 -1
  30. package/dist/command-bus/rpc/rpc-job-cancellation.service.d.ts +82 -0
  31. package/dist/command-bus/rpc/rpc-job-cancellation.service.js +180 -0
  32. package/dist/command-bus/rpc/rpc-job-cancellation.service.js.map +1 -0
  33. package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.d.ts +1 -0
  34. package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js +286 -0
  35. package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js.map +1 -0
  36. package/dist/command-bus/types/index.d.ts +18 -1
  37. package/dist/command-bus/worker/worker-benchmark.js +3 -4
  38. package/dist/command-bus/worker/worker-benchmark.js.map +1 -1
  39. package/dist/examples/auto-config.demo.js +8 -8
  40. package/dist/examples/auto-config.demo.js.map +1 -1
  41. package/dist/examples/rpc-compression.demo.js +32 -37
  42. package/dist/examples/rpc-compression.demo.js.map +1 -1
  43. package/dist/examples/rpc-resilience.demo.d.ts +8 -4
  44. package/dist/examples/rpc-resilience.demo.js +31 -33
  45. package/dist/examples/rpc-resilience.demo.js.map +1 -1
  46. package/dist/examples/rpc-throughput.demo.js +24 -22
  47. package/dist/examples/rpc-throughput.demo.js.map +1 -1
  48. package/dist/examples/rpc.demo.js +47 -53
  49. package/dist/examples/rpc.demo.js.map +1 -1
  50. package/dist/pp-command-bus-1.5.0.tgz +0 -0
  51. package/dist/shared/config/base-config.d.ts +11 -0
  52. package/dist/shared/config/base-config.js +14 -0
  53. package/dist/shared/config/base-config.js.map +1 -1
  54. package/dist/shared/config/base-config.spec.js +102 -16
  55. package/dist/shared/config/base-config.spec.js.map +1 -1
  56. package/dist/shared/redis/redis-connection-factory.d.ts +5 -0
  57. package/dist/shared/redis/redis-connection-factory.js +19 -5
  58. package/dist/shared/redis/redis-connection-factory.js.map +1 -1
  59. package/package.json +1 -1
  60. package/dist/pp-command-bus-1.3.2.tgz +0 -0
package/README.md CHANGED
@@ -18,6 +18,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
18
18
  - ✅ **TypeScript** - pełne wsparcie typów i strict mode
19
19
  - ✅ **Memory leak protection** - zaawansowana diagnostyka i cleanup
20
20
  - ✅ **Command logging** - opcjonalne logowanie komend do plików JSONL
21
+ - ✅ **RPC Job Cancellation** - automatyczne usuwanie niepodjętych jobów RPC przy timeout
21
22
  - ✅ **Modularna architektura** - komponenty zgodne z zasadami SOLID, DDD i SRP
22
23
 
23
24
  ## Architektura Systemu
@@ -67,6 +68,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
67
68
  - **Shared Subscriber** - jeden subscriber dla wszystkich RPC calls (pattern matching)
68
69
  - **Process Isolation** - UUID procesu Node.js dla izolacji odpowiedzi między procesami
69
70
  - **Timeout Management** - automatyczne cleanup po timeout
71
+ - **Job Cancellation** - usuwanie niepodjętych jobów przy timeout (optymalizacja zasobów)
70
72
  - **Multiplexing** - routing odpowiedzi do odpowiednich promises
71
73
  - **Kanały**: `rpc:response:{processId}:{correlationId}`
72
74
 
@@ -83,11 +85,13 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
83
85
  - **Odpowiedzialność**: Wykonywanie handlerów komend i obsługa odpowiedzi RPC
84
86
  - **Flow przetwarzania**:
85
87
  1. Dekompresja payloadu (jeśli skompresowany)
86
- 2. Rekonstrukcja obiektów Date
87
- 3. Opcjonalne logowanie komendy
88
- 4. Wykonanie handlera
89
- 5. Wysłanie odpowiedzi RPC przez Pub/Sub (jeśli RPC)
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)
90
93
  - **Kompresja odpowiedzi**: automatyczna kompresja przez PayloadCompressionService
94
+ - **RPC Cancellation**: pomijanie jobów oznaczonych jako anulowane (timeout)
91
95
 
92
96
  #### 5. **QueueManager** (zarządzanie kolejkami)
93
97
  - **Odpowiedzialność**: Cache kolejek BullMQ dla optymalizacji pamięci
@@ -106,13 +110,24 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
106
110
  - `decompress(data, compressed)` - generyczna dekompresja
107
111
  - **Współdzielony serwis**: jedna instancja dla całego CommandBus
108
112
 
109
- #### 7. **CommandLogger** (opcjonalne logowanie)
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)
110
125
  - **Odpowiedzialność**: Persystencja komend do plików JSONL
111
126
  - **Format**: `{timestamp}.jsonl` - rotacja co godzinę
112
127
  - **Zawartość**: pełny payload komendy z metadanymi
113
128
  - **Aktywacja**: przez ENV `COMMAND_BUS_LOG=./command-logs`
114
129
 
115
- #### 8. **AutoConfigOptimizer** (auto-optymalizacja)
130
+ #### 9. **AutoConfigOptimizer** (auto-optymalizacja)
116
131
  - **Odpowiedzialność**: Obliczanie optymalnego concurrency na podstawie zasobów systemowych
117
132
  - **Heurystyka**:
118
133
  - I/O-heavy workload (Redis/BullMQ)
@@ -120,7 +135,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
120
135
  - Zakładane 512MB RAM per worker
121
136
  - **Aktywacja**: domyślnie włączone (ENV `COMMAND_BUS_AUTO_OPTIMIZE=false` wyłącza)
122
137
 
123
- #### 9. **RedisConnectionFactory** (fabryka połączeń Redis)
138
+ #### 10. **RedisConnectionFactory** (fabryka połączeń Redis)
124
139
  - **Odpowiedzialność**: Tworzenie połączeń Redis z wbudowaną obsługą błędów i eventów
125
140
  - **Zgodność z SRP**: CommandBus nie zarządza bezpośrednio połączeniami
126
141
  - **Kluczowe funkcje**:
@@ -256,12 +271,9 @@ const commandBus = new CommandBus(config);
256
271
  ### 2. Definiowanie komend
257
272
 
258
273
  ```typescript
259
- class CreateUserCommand extends Command {
260
- constructor(
261
- public readonly email: string,
262
- public readonly name: string,
263
- ) {
264
- super();
274
+ class CreateUserCommand extends Command<{ email: string; name: string }> {
275
+ constructor(payload: { email: string; name: string }) {
276
+ super(payload);
265
277
  }
266
278
  }
267
279
  ```
@@ -270,10 +282,11 @@ class CreateUserCommand extends Command {
270
282
 
271
283
  ```typescript
272
284
  commandBus.handle(CreateUserCommand, async (command) => {
273
- console.log(`Creating user: ${command.name} (${command.email})`);
285
+ const { name, email } = command.__payload;
286
+ console.log(`Creating user: ${name} (${email})`);
274
287
 
275
288
  // Twoja logika biznesowa
276
- const user = await createUser(command.email, command.name);
289
+ const user = await createUser(email, name);
277
290
 
278
291
  // Zwróć wynik (opcjonalnie)
279
292
  return { userId: user.id };
@@ -283,7 +296,10 @@ commandBus.handle(CreateUserCommand, async (command) => {
283
296
  ### 4. Wysyłanie komend (Fire-and-forget)
284
297
 
285
298
  ```typescript
286
- const command = new CreateUserCommand('jan.kowalski@example.com', 'Jan Kowalski');
299
+ const command = new CreateUserCommand({
300
+ email: 'jan.kowalski@example.com',
301
+ name: 'Jan Kowalski'
302
+ });
287
303
 
288
304
  // Wyślij komendę bez oczekiwania na wynik
289
305
  await commandBus.dispatch(command);
@@ -307,6 +323,8 @@ Biblioteka wspiera konfigurację poprzez zmienne środowiskowe z prefiksem `COMM
307
323
  | Zmienna | Typ | Wartość Domyślna | Opis |
308
324
  |---------|-----|------------------|------|
309
325
  | `REDIS_URL` | string | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB (wspiera username, password, db) |
326
+ | `REDIS_RETRY_DELAY` | number | `5000` | Opóźnienie między próbami reconnect do Redis w milisekundach |
327
+ | `REDIS_MAX_RETRIES` | number | `0` | Maksymalna liczba prób reconnect (0 = nieskończoność) |
310
328
  | `LOG_LEVEL` | enum | `log` | Poziom logowania: `debug`, `log`, `warn`, `error` |
311
329
  | `COMMAND_BUS_CONCURRENCY` | number | `1` (lub auto) | Liczba równoległych workerów do przetwarzania komend |
312
330
  | `COMMAND_BUS_MAX_ATTEMPTS` | number | `1` | Maksymalna liczba prób przetworzenia zadania |
@@ -332,6 +350,10 @@ Dla kompatybilności wstecznej obsługiwane są również prefiksy `EVENT_BUS_*`
332
350
  # Połączenie Redis
333
351
  REDIS_URL=redis://username:password@localhost:6379/0
334
352
 
353
+ # Strategia reconnect Redis (stałe opóźnienie)
354
+ REDIS_RETRY_DELAY=5000 # 5 sekund między próbami (domyślnie)
355
+ REDIS_MAX_RETRIES=0 # 0 = nieskończoność (domyślnie)
356
+
335
357
  # Poziom logowania
336
358
  LOG_LEVEL=log # debug | log | warn | error
337
359
 
@@ -528,7 +550,10 @@ Wysyła komendę do kolejki bez oczekiwania na wynik (fire-and-forget).
528
550
  4. Natychmiastowy return
529
551
 
530
552
  ```typescript
531
- await commandBus.dispatch(new CreateUserCommand('email@example.com', 'Jan Kowalski'));
553
+ await commandBus.dispatch(new CreateUserCommand({
554
+ email: 'email@example.com',
555
+ name: 'Jan Kowalski'
556
+ }));
532
557
  ```
533
558
 
534
559
  #### `call<T>(command: Command, timeout?: number): Promise<T>`
@@ -561,7 +586,8 @@ Rejestruje handler dla określonej klasy komendy. Tylko jeden handler per typ ko
561
586
 
562
587
  ```typescript
563
588
  commandBus.handle(CreateUserCommand, async (command) => {
564
- const user = await createUser(command.email, command.name);
589
+ const { email, name } = command.__payload;
590
+ const user = await createUser(email, name);
565
591
  return { userId: user.id };
566
592
  });
567
593
  ```
@@ -582,23 +608,105 @@ await commandBus.close();
582
608
 
583
609
  ### Command
584
610
 
585
- Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command`:
611
+ Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command<T>` gdzie `T` to typ danych biznesowych:
586
612
 
587
613
  ```typescript
588
- class MyCommand extends Command {
589
- constructor(public readonly data: string) {
590
- super();
614
+ class MyCommand extends Command<{ data: string }> {
615
+ constructor(payload: { data: string }) {
616
+ super(payload);
591
617
  }
592
618
  }
619
+
620
+ // Uzycie
621
+ const cmd = new MyCommand({ data: 'hello' });
622
+ console.log(cmd.__payload.data); // 'hello'
623
+ ```
624
+
625
+ #### Struktura Komendy
626
+
627
+ Kazda komenda po serializacji ma nastepujaca strukture:
628
+
629
+ ```typescript
630
+ {
631
+ "__name": "MyCommand", // Nazwa klasy komendy
632
+ "__id": "550e8400-e29b-41d4-a716-446655440000", // UUID komendy
633
+ "__time": 1706347200000, // Timestamp utworzenia (ms)
634
+ "__payload": { // Dane biznesowe komendy
635
+ "data": "hello"
636
+ }
637
+ }
638
+ ```
639
+
640
+ #### Opis Pol Komendy
641
+
642
+ | Pole | Typ | Opis |
643
+ |------|-----|------|
644
+ | `__name` | `string` | Nazwa klasy komendy (automatycznie ustawiana z `constructor.name`). Uzywana do routowania do odpowiedniego handlera. |
645
+ | `__id` | `string` | Unikalny identyfikator komendy (UUID v4). Generowany automatycznie przy tworzeniu komendy. Uzywany do korelacji RPC i logowania. |
646
+ | `__time` | `number` | Timestamp utworzenia komendy w milisekundach (`Date.now()`). Uzywany do audytu i debugowania. |
647
+ | `__payload` | `T` | Dane biznesowe komendy. Wszystkie dane specyficzne dla komendy powinny byc przechowywane tutaj. Typ `T` jest generyczny i definiowany przy tworzeniu klasy komendy. |
648
+
649
+ #### Przyklad Definicji Komendy
650
+
651
+ ```typescript
652
+ // Komenda z prostym payloadem
653
+ class CreateUserCommand extends Command<{ name: string; email: string }> {
654
+ constructor(payload: { name: string; email: string }) {
655
+ super(payload);
656
+ }
657
+ }
658
+
659
+ // Komenda ze zlozonym payloadem
660
+ class ProcessOrderCommand extends Command<{
661
+ orderId: string;
662
+ items: Array<{ productId: string; quantity: number }>;
663
+ shippingAddress: {
664
+ street: string;
665
+ city: string;
666
+ postalCode: string;
667
+ };
668
+ }> {
669
+ constructor(payload: {
670
+ orderId: string;
671
+ items: Array<{ productId: string; quantity: number }>;
672
+ shippingAddress: { street: string; city: string; postalCode: string };
673
+ }) {
674
+ super(payload);
675
+ }
676
+ }
677
+
678
+ // Uzycie
679
+ const createUser = new CreateUserCommand({
680
+ name: 'Jan Kowalski',
681
+ email: 'jan@example.com'
682
+ });
683
+
684
+ console.log(createUser.__name); // 'CreateUserCommand'
685
+ console.log(createUser.__id); // '550e8400-e29b-41d4-...'
686
+ console.log(createUser.__time); // 1706347200000
687
+ console.log(createUser.__payload.name); // 'Jan Kowalski'
688
+ console.log(createUser.__payload.email); // 'jan@example.com'
593
689
  ```
594
690
 
595
- **Właściwości automatyczne**:
596
- - `__id` - unikalny UUID (randomUUID)
597
- - `__name` - nazwa klasy komendy (constructor.name)
598
- - `__time` - timestamp utworzenia (Date.now())
691
+ #### Dostep do Danych w Handlerze
692
+
693
+ ```typescript
694
+ commandBus.handle(CreateUserCommand, async (command) => {
695
+ // Dostep do danych biznesowych przez __payload
696
+ const { name, email } = command.__payload;
697
+
698
+ // Dostep do metadanych komendy
699
+ console.log(`Processing command ${command.__id} (${command.__name})`);
700
+ console.log(`Created at: ${new Date(command.__time).toISOString()}`);
701
+
702
+ // Logika biznesowa
703
+ const user = await createUser(name, email);
704
+ return { userId: user.id };
705
+ });
706
+ ```
599
707
 
600
708
  **Metody statyczne**:
601
- - `reconstructDates(obj)` - rekonstrukcja obiektów Date z serializowanych danych
709
+ - `reconstructDates(obj)` - rekonstrukcja obiektow Date z serializowanych danych (uzywane wewnetrznie przez JobProcessor)
602
710
 
603
711
  ### CommandBusConfig
604
712
 
@@ -621,19 +729,19 @@ interface CommandBusConfigOptions {
621
729
 
622
730
  ## Przykłady Użycia
623
731
 
624
- ### Podstawowy przykład z RPC
732
+ ### Podstawowy przyklad z RPC
625
733
 
626
734
  ```typescript
627
735
  import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
628
736
 
629
- // Definicja komendy
630
- class CalculateCommand extends Command {
631
- constructor(
632
- public readonly a: number,
633
- public readonly b: number,
634
- public readonly operation: 'add' | 'multiply',
635
- ) {
636
- super();
737
+ // Definicja komendy z payloadem
738
+ class CalculateCommand extends Command<{
739
+ a: number;
740
+ b: number;
741
+ operation: 'add' | 'multiply';
742
+ }> {
743
+ constructor(payload: { a: number; b: number; operation: 'add' | 'multiply' }) {
744
+ super(payload);
637
745
  }
638
746
  }
639
747
 
@@ -645,64 +753,69 @@ const commandBus = new CommandBus(config);
645
753
 
646
754
  // Rejestracja handlera
647
755
  commandBus.handle(CalculateCommand, async (command) => {
648
- console.log(`Calculating: ${command.a} ${command.operation} ${command.b}`);
756
+ const { a, b, operation } = command.__payload;
757
+ console.log(`Calculating: ${a} ${operation} ${b}`);
649
758
 
650
- switch (command.operation) {
759
+ switch (operation) {
651
760
  case 'add':
652
- return command.a + command.b;
761
+ return a + b;
653
762
  case 'multiply':
654
- return command.a * command.b;
763
+ return a * b;
655
764
  }
656
765
  });
657
766
 
658
767
  // Fire-and-forget
659
- await commandBus.dispatch(new CalculateCommand(5, 3, 'add'));
768
+ await commandBus.dispatch(new CalculateCommand({ a: 5, b: 3, operation: 'add' }));
660
769
 
661
770
  // RPC - czekamy na wynik
662
771
  const result = await commandBus.call<number>(
663
- new CalculateCommand(5, 3, 'multiply'),
772
+ new CalculateCommand({ a: 5, b: 3, operation: 'multiply' }),
664
773
  5000 // timeout 5s
665
774
  );
666
775
  console.log(`Result: ${result}`); // Result: 15
667
776
  ```
668
777
 
669
- ### Równoległe wywołania RPC
778
+ ### Rownolegle wywolania RPC
670
779
 
671
780
  ```typescript
672
- // Wiele równoległych RPC calls
781
+ // Wiele rownoległych RPC calls
673
782
  const [result1, result2, result3] = await Promise.all([
674
- commandBus.call<number>(new CalculateCommand(10, 5, 'add')),
675
- commandBus.call<UserInfo>(new GetUserInfoCommand('user-1')),
676
- commandBus.call<ValidationResult>(new ValidateUserCommand('jan@example.com', 30)),
783
+ commandBus.call<number>(new CalculateCommand({ a: 10, b: 5, operation: 'add' })),
784
+ commandBus.call<UserInfo>(new GetUserInfoCommand({ userId: 'user-1' })),
785
+ commandBus.call<ValidationResult>(new ValidateUserCommand({ email: 'jan@example.com', age: 30 })),
677
786
  ]);
678
787
 
679
788
  console.log('All results:', { result1, result2, result3 });
680
789
  ```
681
790
 
682
- ### Obsługa błędów
791
+ ### Obsluga bledow
683
792
 
684
793
  ```typescript
685
794
  commandBus.handle(CreateUserCommand, async (command) => {
686
795
  try {
796
+ const { email, name } = command.__payload;
797
+
687
798
  // Walidacja
688
- if (!command.email.includes('@')) {
799
+ if (!email.includes('@')) {
689
800
  throw new Error('Invalid email format');
690
801
  }
691
802
 
692
- const user = await createUser(command.email, command.name);
803
+ const user = await createUser(email, name);
693
804
  return { userId: user.id };
694
805
  } catch (error) {
695
- // Loguj błąd
806
+ // Loguj blad
696
807
  console.error('Failed to create user:', error);
697
808
 
698
- // Rzuć błąd - BullMQ spróbuje ponownie (maxAttempts)
809
+ // Rzuc blad - BullMQ sprobuje ponownie (maxAttempts)
699
810
  throw error;
700
811
  }
701
812
  });
702
813
 
703
- // Obsługa błędów w RPC
814
+ // Obsluga bledow w RPC
704
815
  try {
705
- const result = await commandBus.call(new CreateUserCommand('invalid-email', 'Jan'));
816
+ const result = await commandBus.call(
817
+ new CreateUserCommand({ email: 'invalid-email', name: 'Jan' })
818
+ );
706
819
  } catch (error) {
707
820
  console.error('RPC failed:', error.message);
708
821
  }
@@ -809,6 +922,45 @@ const config = new CommandBusConfig({
809
922
  - Debugging produkcyjnych problemów
810
923
  - Analiza przepływu komend
811
924
 
925
+ ### 6. RPC Job Cancellation
926
+
927
+ Automatyczne usuwanie niepodjętych jobów RPC przy timeout:
928
+
929
+ ```
930
+ RPC Timeout Flow:
931
+
932
+ User Code RpcCoordinator Redis JobProcessor
933
+ │ │ │ │
934
+ │── call(cmd) ─────▶│ │ │
935
+ │ │── registerCall ──▶│ │
936
+ │ │── queue.add ─────▶│ │
937
+ │ │ │ │
938
+ │ [timeout] │ │ │
939
+ │ │ │ │
940
+ │ │── markCancelled ─▶│ SET rpc:cancelled:xxx
941
+ │ │── tryRemoveJob ──▶│ (próba usunięcia)
942
+ │◀── Error ─────────│ │ │
943
+ │ │ │ │
944
+ │ │ │ [job picked up] │
945
+ │ │ │──────────────────▶│
946
+ │ │ │ │── isCancelled?
947
+ │ │ │◀──────────────────│ → true
948
+ │ │ │ │── SKIP handler
949
+ │ │ │ │── clearCancellation
950
+ ```
951
+
952
+ **Kluczowe cechy**:
953
+ - **Dwufazowe anulowanie**: Flaga Redis + próba usunięcia joba z kolejki
954
+ - **Graceful degradation**: Błędy Redis nie blokują przetwarzania
955
+ - **TTL 24h**: Automatyczne wygaśnięcie kluczy cancellation
956
+ - **Aktywne czyszczenie**: Klucze usuwane po przetworzeniu lub usunięciu joba
957
+ - **Kompatybilność wsteczna**: Funkcjonalność jest opcjonalna
958
+
959
+ **Korzyści**:
960
+ - Oszczędność zasobów - nieprzetworzone joby nie obciążają workerów
961
+ - Brak efektów ubocznych - handler nie wykonuje się dla timeout'owanych RPC
962
+ - Lepsza diagnostyka - logi pokazują pominięte joby
963
+
812
964
  ## Best Practices
813
965
 
814
966
  ### 1. Używaj TypeScript
@@ -826,18 +978,20 @@ const result = await commandBus.call<UserCreatedResult>(command);
826
978
 
827
979
  ### 2. Idempotentne handlery
828
980
 
829
- Handlery powinny być idempotentne (wielokrotne wykonanie = ten sam rezultat):
981
+ Handlery powinny byc idempotentne (wielokrotne wykonanie = ten sam rezultat):
830
982
 
831
983
  ```typescript
832
984
  commandBus.handle(CreateUserCommand, async (command) => {
833
- // Sprawdź czy użytkownik już istnieje
834
- const existing = await findUserByEmail(command.email);
985
+ const { email, name } = command.__payload;
986
+
987
+ // Sprawdz czy uzytkownik juz istnieje
988
+ const existing = await findUserByEmail(email);
835
989
  if (existing) {
836
- return { userId: existing.id }; // Zwróć istniejącego
990
+ return { userId: existing.id }; // Zwroc istniejacego
837
991
  }
838
992
 
839
- // Utwórz nowego
840
- const user = await createUser(command.email, command.name);
993
+ // Utworz nowego
994
+ const user = await createUser(email, name);
841
995
  return { userId: user.id };
842
996
  });
843
997
  ```
@@ -857,17 +1011,19 @@ const result2 = await commandBus.call(longRunningCommand, 60000); // 60s dla dł
857
1011
 
858
1012
  ```typescript
859
1013
  commandBus.handle(CreateUserCommand, async (command) => {
860
- // Walidacja na początku
861
- if (!command.email || !command.email.includes('@')) {
1014
+ const { email, name } = command.__payload;
1015
+
1016
+ // Walidacja na poczatku
1017
+ if (!email || !email.includes('@')) {
862
1018
  throw new Error('Invalid email');
863
1019
  }
864
1020
 
865
- if (!command.name || command.name.length < 2) {
1021
+ if (!name || name.length < 2) {
866
1022
  throw new Error('Invalid name');
867
1023
  }
868
1024
 
869
1025
  // Logika biznesowa
870
- return await createUser(command.email, command.name);
1026
+ return await createUser(email, name);
871
1027
  });
872
1028
  ```
873
1029
 
@@ -875,14 +1031,16 @@ commandBus.handle(CreateUserCommand, async (command) => {
875
1031
 
876
1032
  ```typescript
877
1033
  commandBus.handle(CreateUserCommand, async (command) => {
1034
+ const { email, name } = command.__payload;
1035
+
878
1036
  console.log('Processing CreateUserCommand', {
879
1037
  commandId: command.__id,
880
1038
  timestamp: command.__time,
881
- email: command.email,
1039
+ email: email,
882
1040
  });
883
1041
 
884
1042
  // Logika biznesowa
885
- const user = await createUser(command.email, command.name);
1043
+ const user = await createUser(email, name);
886
1044
 
887
1045
  console.log('User created successfully', {
888
1046
  commandId: command.__id,
@@ -900,6 +1058,13 @@ commandBus.handle(CreateUserCommand, async (command) => {
900
1058
  ```typescript
901
1059
  import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
902
1060
 
1061
+ // Definicja komendy testowej
1062
+ class CreateUserCommand extends Command<{ email: string; name: string }> {
1063
+ constructor(payload: { email: string; name: string }) {
1064
+ super(payload);
1065
+ }
1066
+ }
1067
+
903
1068
  describe('User Commands', () => {
904
1069
  let commandBus: CommandBus;
905
1070
 
@@ -907,7 +1072,7 @@ describe('User Commands', () => {
907
1072
  const config = new CommandBusConfig({
908
1073
  redisUrl: 'redis://localhost:6379',
909
1074
  logger: console,
910
- logLevel: 'error', // Tylko błędy w testach
1075
+ logLevel: 'error', // Tylko bledy w testach
911
1076
  });
912
1077
 
913
1078
  commandBus = new CommandBus(config);
@@ -923,7 +1088,10 @@ describe('User Commands', () => {
923
1088
  });
924
1089
 
925
1090
  it('should create user', async () => {
926
- const command = new CreateUserCommand('test@example.com', 'Test User');
1091
+ const command = new CreateUserCommand({
1092
+ email: 'test@example.com',
1093
+ name: 'Test User'
1094
+ });
927
1095
  const result = await commandBus.call<{ userId: string }>(command, 5000);
928
1096
 
929
1097
  expect(result.userId).toBeDefined();
@@ -932,9 +1100,9 @@ describe('User Commands', () => {
932
1100
 
933
1101
  it('should handle multiple commands in parallel', async () => {
934
1102
  const commands = [
935
- new CreateUserCommand('user1@example.com', 'User 1'),
936
- new CreateUserCommand('user2@example.com', 'User 2'),
937
- new CreateUserCommand('user3@example.com', 'User 3'),
1103
+ new CreateUserCommand({ email: 'user1@example.com', name: 'User 1' }),
1104
+ new CreateUserCommand({ email: 'user2@example.com', name: 'User 2' }),
1105
+ new CreateUserCommand({ email: 'user3@example.com', name: 'User 3' }),
938
1106
  ];
939
1107
 
940
1108
  const results = await Promise.all(
@@ -1037,7 +1205,8 @@ src/
1037
1205
  │ │ └── queue-manager.ts
1038
1206
  │ ├── rpc/ # RPC coordination
1039
1207
  │ │ ├── rpc-coordinator.ts
1040
- │ │ └── payload-compression.service.ts
1208
+ │ │ ├── payload-compression.service.ts
1209
+ │ │ └── rpc-job-cancellation.service.ts # Anulowanie jobów RPC przy timeout
1041
1210
  │ ├── worker/ # Worker orchestration
1042
1211
  │ │ ├── worker-orchestrator.ts
1043
1212
  │ │ ├── worker-benchmark.ts
@@ -1139,6 +1308,7 @@ Szczegółowa dokumentacja architektury systemu, wzorców projektowych i zasad S
1139
1308
  - **WorkerOrchestrator** - orkiestracja workerów, dynamiczne concurrency, benchmark
1140
1309
  - **JobProcessor** - przetwarzanie jobów i wykonanie handlerów
1141
1310
  - **RpcCoordinator** - zarządzanie wywołaniami RPC przez Redis Pub/Sub
1311
+ - **RpcJobCancellationService** - anulowanie niepodjętych jobów RPC przy timeout
1142
1312
  - **JobOptionsBuilder** - konfiguracja opcji dla jobów BullMQ
1143
1313
  - **CommandLogger** - persystencja komend do plików JSONL
1144
1314
  - **PayloadCompressionService** - automatyczna kompresja gzip