nestjs-temporal-core 3.0.11 → 3.0.12

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.0.12] - 2025-11-07
9
+
10
+ ### Added
11
+ - **Multiple Workers Support**: Configure and manage multiple workers with different task queues in the same process
12
+ - New `workers` array property in `TemporalOptions` for defining multiple worker configurations
13
+ - `getAllWorkers()`: Get information about all registered workers
14
+ - `getWorker(taskQueue)`: Get a specific worker by task queue name
15
+ - `getWorkerStatusByTaskQueue(taskQueue)`: Get status of a specific worker
16
+ - `startWorkerByTaskQueue(taskQueue)`: Start a specific worker
17
+ - `stopWorkerByTaskQueue(taskQueue)`: Stop a specific worker
18
+ - `registerWorker(workerDef)`: Dynamically register new workers at runtime
19
+ - `getWorkerManager()`: Access worker manager service
20
+ - `getConnection()`: Access native Temporal connection for custom worker creation
21
+ - New interfaces: `WorkerDefinition`, `MultipleWorkersInfo`, `CreateWorkerResult`, `WorkerInstance`
22
+ - Comprehensive migration guide in README for upgrading from single to multiple workers
23
+ - Documentation for manual worker creation using native Temporal SDK
24
+
25
+ ### Changed
26
+ - Refactored `TemporalWorkerManagerService` to support both legacy single worker and new multiple workers
27
+ - Enhanced worker lifecycle management with per-worker state tracking
28
+ - Improved worker initialization with better error handling
29
+ - All interfaces consolidated in `src/interfaces.ts` (moved `WorkerInstance` from service file)
30
+
31
+ ### Fixed
32
+ - No breaking changes - fully backward compatible with v3.0.11
33
+ - Legacy single worker configuration continues to work without modifications
34
+
35
+ ## [3.0.11] - 2025-11-01
36
+
37
+ ### Fixed
38
+ - Improved stability and error handling
39
+ - Enhanced logging for better debugging
40
+
41
+ ## [3.0.10] - 2025-10-15
42
+
43
+ ### Added
44
+ - Enhanced monitoring capabilities
45
+ - Improved health check endpoints
46
+
47
+ ### Changed
48
+ - Updated internal architecture for better performance
49
+
50
+ ## [3.0.0] - 2025-09-01
51
+
52
+ ### Added
53
+ - Complete rewrite with modular architecture
54
+ - Auto-discovery of activities via decorators
55
+ - Enhanced monitoring and health checks
56
+ - Comprehensive TypeScript support
57
+ - Production-ready error handling
58
+
59
+ ### Changed
60
+ - Breaking changes from v2.x - see migration guide
61
+
62
+ ## [2.x] - Historical Versions
63
+
64
+ See previous releases for v2.x changelog.
65
+
66
+ ---
67
+
68
+ ## Migration Guide
69
+
70
+ ### Upgrading from 3.0.11 to 3.0.12
71
+
72
+ No breaking changes. All existing code continues to work. See README.md for new multiple workers features.
73
+
74
+ ### Upgrading from 3.0.10 to 3.0.11
75
+
76
+ No breaking changes. Minor improvements and bug fixes.
77
+
78
+ ### Upgrading from 2.x to 3.x
79
+
80
+ Major rewrite - see full migration guide in documentation.
package/README.md CHANGED
@@ -362,6 +362,136 @@ TemporalModule.register({
362
362
  })
363
363
  ```
364
364
 
365
+ ### Multiple Workers Configuration
366
+
367
+ **New in 3.0.12**: Support for multiple workers with different task queues in the same process.
368
+
369
+ ```typescript
370
+ TemporalModule.register({
371
+ connection: {
372
+ address: 'localhost:7233',
373
+ namespace: 'default',
374
+ },
375
+ workers: [
376
+ {
377
+ taskQueue: 'payments-queue',
378
+ workflowsPath: require.resolve('./workflows/payments'),
379
+ activityClasses: [PaymentActivity, RefundActivity],
380
+ autoStart: true,
381
+ workerOptions: {
382
+ maxConcurrentActivityTaskExecutions: 100,
383
+ },
384
+ },
385
+ {
386
+ taskQueue: 'notifications-queue',
387
+ workflowsPath: require.resolve('./workflows/notifications'),
388
+ activityClasses: [EmailActivity, SmsActivity],
389
+ autoStart: true,
390
+ workerOptions: {
391
+ maxConcurrentActivityTaskExecutions: 50,
392
+ },
393
+ },
394
+ {
395
+ taskQueue: 'background-jobs',
396
+ workflowsPath: require.resolve('./workflows/jobs'),
397
+ activityClasses: [DataProcessingActivity],
398
+ autoStart: false, // Start manually later
399
+ },
400
+ ],
401
+ logLevel: 'info',
402
+ enableLogger: true,
403
+ })
404
+ ```
405
+
406
+ #### Accessing Multiple Workers
407
+
408
+ ```typescript
409
+ import { Injectable } from '@nestjs/common';
410
+ import { TemporalService } from 'nestjs-temporal-core';
411
+
412
+ @Injectable()
413
+ export class WorkerManagementService {
414
+ constructor(private readonly temporal: TemporalService) {}
415
+
416
+ async checkWorkerStatus() {
417
+ // Get all workers info
418
+ const workersInfo = this.temporal.getAllWorkers();
419
+ console.log(`Total workers: ${workersInfo.totalWorkers}`);
420
+ console.log(`Running workers: ${workersInfo.runningWorkers}`);
421
+
422
+ // Get specific worker status
423
+ const paymentWorkerStatus = this.temporal.getWorkerStatusByTaskQueue('payments-queue');
424
+ if (paymentWorkerStatus?.isHealthy) {
425
+ console.log('Payment worker is healthy');
426
+ }
427
+ }
428
+
429
+ async controlWorkers() {
430
+ // Start a specific worker
431
+ await this.temporal.startWorkerByTaskQueue('background-jobs');
432
+
433
+ // Stop a specific worker
434
+ await this.temporal.stopWorkerByTaskQueue('notifications-queue');
435
+ }
436
+
437
+ async registerNewWorker() {
438
+ // Dynamically register a new worker at runtime
439
+ const result = await this.temporal.registerWorker({
440
+ taskQueue: 'new-queue',
441
+ workflowsPath: require.resolve('./workflows/new'),
442
+ activityClasses: [NewActivity],
443
+ autoStart: true,
444
+ });
445
+
446
+ if (result.success) {
447
+ console.log(`Worker registered for queue: ${result.taskQueue}`);
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
453
+ ### Manual Worker Creation (Advanced)
454
+
455
+ For users who need full control, you can access the native Temporal connection to create custom workers:
456
+
457
+ ```typescript
458
+ import { Injectable, OnModuleInit } from '@nestjs/common';
459
+ import { TemporalService } from 'nestjs-temporal-core';
460
+ import { Worker } from '@temporalio/worker';
461
+
462
+ @Injectable()
463
+ export class CustomWorkerService implements OnModuleInit {
464
+ private customWorker: Worker;
465
+
466
+ constructor(private readonly temporal: TemporalService) {}
467
+
468
+ async onModuleInit() {
469
+ const workerManager = this.temporal.getWorkerManager();
470
+ const connection = workerManager.getConnection();
471
+
472
+ if (!connection) {
473
+ throw new Error('No connection available');
474
+ }
475
+
476
+ // Create your custom worker using the native Temporal SDK
477
+ this.customWorker = await Worker.create({
478
+ connection,
479
+ taskQueue: 'custom-task-queue',
480
+ namespace: 'default',
481
+ workflowsPath: require.resolve('./workflows/custom'),
482
+ activities: {
483
+ myCustomActivity: async (data: string) => {
484
+ return `Processed: ${data}`;
485
+ },
486
+ },
487
+ });
488
+
489
+ // Start the worker
490
+ await this.customWorker.run();
491
+ }
492
+ }
493
+ ```
494
+
365
495
  ### Async Configuration
366
496
 
367
497
  For dynamic configuration using environment variables or config services:
@@ -1425,6 +1555,208 @@ interface ServiceHealth {
1425
1555
  ```
1426
1556
  ```
1427
1557
 
1558
+ ## Migration Guide
1559
+
1560
+ ### Migrating to v3.0.12+ (Multiple Workers Support)
1561
+
1562
+ Version 3.0.12 introduces support for multiple workers without breaking existing single-worker configurations.
1563
+
1564
+ #### No Changes Required for Single Worker
1565
+
1566
+ If you're using a single worker, your existing configuration continues to work without any changes:
1567
+
1568
+ ```typescript
1569
+ // ✅ This still works exactly as before
1570
+ TemporalModule.register({
1571
+ connection: { address: 'localhost:7233' },
1572
+ taskQueue: 'my-queue',
1573
+ worker: {
1574
+ workflowsPath: require.resolve('./workflows'),
1575
+ activityClasses: [MyActivity],
1576
+ },
1577
+ })
1578
+ ```
1579
+
1580
+ #### Migrating to Multiple Workers
1581
+
1582
+ **Before (v3.0.10):**
1583
+ ```typescript
1584
+ // You had to create custom workers manually
1585
+ @Injectable()
1586
+ export class ScheduleService implements OnModuleInit {
1587
+ private customWorker: Worker;
1588
+
1589
+ constructor(private temporal: TemporalService) {}
1590
+
1591
+ async onModuleInit() {
1592
+ // This pattern required accessing internal APIs
1593
+ const workerManager = this.temporal.getWorkerManager();
1594
+ const connection = workerManager?.getConnection(); // This wasn't available!
1595
+
1596
+ // Manual worker creation...
1597
+ }
1598
+ }
1599
+ ```
1600
+
1601
+ **After (v3.0.12):**
1602
+ ```typescript
1603
+ // Option 1: Configure multiple workers in module
1604
+ TemporalModule.register({
1605
+ connection: { address: 'localhost:7233' },
1606
+ workers: [
1607
+ {
1608
+ taskQueue: 'main-queue',
1609
+ workflowsPath: require.resolve('./workflows/main'),
1610
+ activityClasses: [MainActivity],
1611
+ },
1612
+ {
1613
+ taskQueue: 'schedule-queue',
1614
+ workflowsPath: require.resolve('./workflows/schedule'),
1615
+ activityClasses: [ScheduleActivity],
1616
+ },
1617
+ ],
1618
+ })
1619
+
1620
+ // Option 2: Get native connection for manual worker creation
1621
+ @Injectable()
1622
+ export class CustomWorkerService implements OnModuleInit {
1623
+ constructor(private temporal: TemporalService) {}
1624
+
1625
+ async onModuleInit() {
1626
+ const workerManager = this.temporal.getWorkerManager();
1627
+ const connection = workerManager.getConnection(); // Now available!
1628
+
1629
+ if (!connection) return;
1630
+
1631
+ const customWorker = await Worker.create({
1632
+ connection, // Native NativeConnection object
1633
+ taskQueue: 'custom-queue',
1634
+ workflowsPath: require.resolve('./workflows/custom'),
1635
+ activities: {
1636
+ myActivity: async (data: string) => data,
1637
+ },
1638
+ });
1639
+
1640
+ await customWorker.run();
1641
+ }
1642
+ }
1643
+
1644
+ // Option 3: Register workers dynamically at runtime
1645
+ @Injectable()
1646
+ export class DynamicWorkerService {
1647
+ constructor(private temporal: TemporalService) {}
1648
+
1649
+ async registerNewQueue(taskQueue: string) {
1650
+ const result = await this.temporal.registerWorker({
1651
+ taskQueue,
1652
+ workflowsPath: require.resolve('./workflows/dynamic'),
1653
+ activityClasses: [DynamicActivity],
1654
+ autoStart: true,
1655
+ });
1656
+
1657
+ if (result.success) {
1658
+ console.log(`Worker registered for ${taskQueue}`);
1659
+ }
1660
+ }
1661
+ }
1662
+ ```
1663
+
1664
+ #### New APIs in v3.0.12
1665
+
1666
+ ```typescript
1667
+ // Get native connection for custom worker creation
1668
+ const workerManager = temporal.getWorkerManager();
1669
+ const connection: NativeConnection | null = workerManager.getConnection();
1670
+
1671
+ // Get specific worker by task queue
1672
+ const worker: Worker | null = temporal.getWorker('payments-queue');
1673
+
1674
+ // Get all workers information
1675
+ const workersInfo: MultipleWorkersInfo = temporal.getAllWorkers();
1676
+ console.log(`${workersInfo.runningWorkers}/${workersInfo.totalWorkers} workers running`);
1677
+
1678
+ // Get specific worker status
1679
+ const status: WorkerStatus | null = temporal.getWorkerStatusByTaskQueue('payments-queue');
1680
+
1681
+ // Control specific workers
1682
+ await temporal.startWorkerByTaskQueue('payments-queue');
1683
+ await temporal.stopWorkerByTaskQueue('notifications-queue');
1684
+
1685
+ // Register new worker dynamically
1686
+ const result = await temporal.registerWorker({
1687
+ taskQueue: 'new-queue',
1688
+ workflowsPath: require.resolve('./workflows/new'),
1689
+ activityClasses: [NewActivity],
1690
+ autoStart: true,
1691
+ });
1692
+ ```
1693
+
1694
+ #### Breaking Changes from v3.0.10 to v3.0.11
1695
+
1696
+ If you're upgrading from v3.0.10, note these changes:
1697
+
1698
+ 1. **Internal Architecture**: The internal connection management was refactored. If you were accessing private/internal APIs, those may have changed.
1699
+
1700
+ 2. **getConnection() Now Available**: In v3.0.10, accessing the native connection wasn't possible. This is now officially supported via `getWorkerManager().getConnection()`.
1701
+
1702
+ 3. **No API Removals**: All public APIs from v3.0.10 remain available in v3.0.11+.
1703
+
1704
+ #### Best Practices for Multiple Workers
1705
+
1706
+ ```typescript
1707
+ // 1. Separate workers by domain/responsibility
1708
+ TemporalModule.register({
1709
+ connection: { address: 'localhost:7233' },
1710
+ workers: [
1711
+ {
1712
+ taskQueue: 'payments', // Financial transactions
1713
+ workflowsPath: require.resolve('./workflows/payments'),
1714
+ activityClasses: [PaymentActivity, RefundActivity],
1715
+ workerOptions: {
1716
+ maxConcurrentActivityTaskExecutions: 50,
1717
+ },
1718
+ },
1719
+ {
1720
+ taskQueue: 'notifications', // User notifications
1721
+ workflowsPath: require.resolve('./workflows/notifications'),
1722
+ activityClasses: [EmailActivity, SmsActivity],
1723
+ workerOptions: {
1724
+ maxConcurrentActivityTaskExecutions: 100,
1725
+ },
1726
+ },
1727
+ {
1728
+ taskQueue: 'background-jobs', // Async background processing
1729
+ workflowsPath: require.resolve('./workflows/jobs'),
1730
+ activityClasses: [DataProcessingActivity],
1731
+ autoStart: false, // Start only when needed
1732
+ },
1733
+ ],
1734
+ })
1735
+
1736
+ // 2. Monitor worker health individually
1737
+ @Injectable()
1738
+ export class WorkerHealthService {
1739
+ constructor(private temporal: TemporalService) {}
1740
+
1741
+ async checkAllWorkers() {
1742
+ const allWorkers = this.temporal.getAllWorkers();
1743
+
1744
+ for (const [taskQueue, status] of allWorkers.workers.entries()) {
1745
+ if (!status.isHealthy) {
1746
+ // Alert or restart unhealthy worker
1747
+ await this.restartWorker(taskQueue);
1748
+ }
1749
+ }
1750
+ }
1751
+
1752
+ private async restartWorker(taskQueue: string) {
1753
+ await this.temporal.stopWorkerByTaskQueue(taskQueue);
1754
+ await new Promise(resolve => setTimeout(resolve, 1000));
1755
+ await this.temporal.startWorkerByTaskQueue(taskQueue);
1756
+ }
1757
+ }
1758
+ ```
1759
+
1428
1760
  ## Troubleshooting
1429
1761
 
1430
1762
  ### Common Issues and Solutions
@@ -20,6 +20,14 @@ export interface RetryPolicyConfig {
20
20
  maximumInterval: string;
21
21
  backoffCoefficient: number;
22
22
  }
23
+ export interface WorkerDefinition {
24
+ taskQueue: string;
25
+ workflowsPath?: string;
26
+ workflowBundle?: Record<string, unknown>;
27
+ activityClasses?: Array<Type<object>>;
28
+ autoStart?: boolean;
29
+ workerOptions?: WorkerCreateOptions;
30
+ }
23
31
  export interface TemporalOptions extends LoggerConfig {
24
32
  connection?: {
25
33
  address: string;
@@ -36,6 +44,7 @@ export interface TemporalOptions extends LoggerConfig {
36
44
  autoStart?: boolean;
37
45
  workerOptions?: WorkerCreateOptions;
38
46
  };
47
+ workers?: WorkerDefinition[];
39
48
  autoRestart?: boolean;
40
49
  isGlobal?: boolean;
41
50
  allowConnectionFailure?: boolean;
@@ -178,6 +187,30 @@ export interface WorkerStatus {
178
187
  startedAt?: Date;
179
188
  uptime?: number;
180
189
  }
190
+ export interface MultipleWorkersInfo {
191
+ workers: Map<string, WorkerStatus>;
192
+ totalWorkers: number;
193
+ runningWorkers: number;
194
+ healthyWorkers: number;
195
+ }
196
+ export interface CreateWorkerResult {
197
+ success: boolean;
198
+ taskQueue: string;
199
+ error?: Error;
200
+ worker?: Worker;
201
+ }
202
+ export interface WorkerInstance {
203
+ worker: Worker;
204
+ taskQueue: string;
205
+ namespace: string;
206
+ isRunning: boolean;
207
+ isInitialized: boolean;
208
+ lastError: string | null;
209
+ startedAt: Date | null;
210
+ restartCount: number;
211
+ activities: Map<string, Function>;
212
+ workflowSource: 'bundle' | 'filesystem' | 'registered' | 'none';
213
+ }
181
214
  export interface SignalMethodMetadata {
182
215
  signalName: string;
183
216
  methodName: string;
@@ -1,6 +1,6 @@
1
1
  import { OnModuleInit, OnModuleDestroy, OnApplicationBootstrap } from '@nestjs/common';
2
- import { NativeConnection } from '@temporalio/worker';
3
- import { TemporalOptions, WorkerStatus, WorkerRestartResult, WorkerHealthStatus, WorkerStats, ActivityRegistrationResult } from '../interfaces';
2
+ import { Worker, NativeConnection } from '@temporalio/worker';
3
+ import { TemporalOptions, WorkerStatus, WorkerRestartResult, WorkerHealthStatus, WorkerStats, ActivityRegistrationResult, WorkerDefinition, MultipleWorkersInfo, CreateWorkerResult } from '../interfaces';
4
4
  import { TemporalDiscoveryService } from './temporal-discovery.service';
5
5
  export declare class TemporalWorkerManagerService implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap {
6
6
  private readonly discoveryService;
@@ -10,17 +10,30 @@ export declare class TemporalWorkerManagerService implements OnModuleInit, OnMod
10
10
  private worker;
11
11
  private restartCount;
12
12
  private readonly maxRestarts;
13
- private connection;
14
13
  private isInitialized;
15
14
  private isRunning;
16
15
  private lastError;
17
16
  private startedAt;
18
17
  private readonly activities;
18
+ private readonly workers;
19
+ private connection;
19
20
  private shutdownPromise;
20
21
  constructor(discoveryService: TemporalDiscoveryService, options: TemporalOptions, injectedConnection: NativeConnection | null);
21
22
  onModuleInit(): Promise<void>;
22
23
  onApplicationBootstrap(): Promise<void>;
23
24
  onModuleDestroy(): Promise<void>;
25
+ private initializeMultipleWorkers;
26
+ private createWorkerFromDefinition;
27
+ registerWorker(workerDef: WorkerDefinition): Promise<CreateWorkerResult>;
28
+ getWorker(taskQueue: string): Worker | null;
29
+ getAllWorkers(): MultipleWorkersInfo;
30
+ getWorkerStatusByTaskQueue(taskQueue: string): WorkerStatus | null;
31
+ startWorkerByTaskQueue(taskQueue: string): Promise<void>;
32
+ stopWorkerByTaskQueue(taskQueue: string): Promise<void>;
33
+ getConnection(): NativeConnection | null;
34
+ private getWorkerStatusFromInstance;
35
+ private loadActivitiesForWorker;
36
+ private getWorkflowSourceFromDef;
24
37
  startWorker(): Promise<void>;
25
38
  private runWorkerWithAutoRestart;
26
39
  private autoRestartWorker;