nestjs-temporal-core 3.1.4 → 3.1.6

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 (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +166 -836
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,16 @@
1
1
  # Changelog
2
+ ## [3.1.6] - 2025-11-12
3
+
4
+ ### Changes
5
+
6
+ - chore: update release workflow to generate documentation after version bump; improve README with example project details (15cd112)
7
+
8
+ ## [3.1.5] - 2025-11-12
9
+
10
+ ### Changes
11
+
12
+ - Update documentation for TIMEOUTS and WORKFLOW_PARAMS_METADATA; remove WorkflowRun documentation (c293dc5)
13
+
2
14
  ## [3.1.4] - 2025-11-12
3
15
 
4
16
  ### Changes
package/README.md CHANGED
@@ -1,9 +1,18 @@
1
1
  # NestJS Temporal Core
2
2
 
3
+ <div align="center">
4
+
3
5
  A comprehensive NestJS integration framework for Temporal.io that provides enterprise-ready workflow orchestration with automatic discovery, declarative decorators, and robust monitoring capabilities.
4
6
 
5
7
  [![npm version](https://badge.fury.io/js/nestjs-temporal-core.svg)](https://badge.fury.io/js/nestjs-temporal-core)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![npm downloads](https://img.shields.io/npm/dm/nestjs-temporal-core.svg)](https://www.npmjs.com/package/nestjs-temporal-core)
10
+
11
+ [Documentation](https://harsh-simform.github.io/nestjs-temporal-core/) • [NPM](https://www.npmjs.com/package/nestjs-temporal-core) • [GitHub](https://github.com/harsh-simform/nestjs-temporal-core) • [Example Project](https://github.com/harsh-simform/nestjs-temporal-core-example)
12
+
13
+ </div>
14
+
15
+ ---
7
16
 
8
17
  ## Table of Contents
9
18
 
@@ -13,13 +22,21 @@ A comprehensive NestJS integration framework for Temporal.io that provides enter
13
22
  - [Quick Start](#quick-start)
14
23
  - [Module Variants](#module-variants)
15
24
  - [Configuration](#configuration)
25
+ - [Basic Configuration](#basic-configuration)
26
+ - [Multiple Workers](#multiple-workers-configuration)
27
+ - [Async Configuration](#async-configuration)
28
+ - [TLS Configuration](#tls-configuration-temporal-cloud)
16
29
  - [Core Concepts](#core-concepts)
30
+ - [Activities](#activities)
31
+ - [Workflows](#workflows)
32
+ - [Signals and Queries](#signals-and-queries)
17
33
  - [API Reference](#api-reference)
18
34
  - [Examples](#examples)
19
35
  - [Advanced Usage](#advanced-usage)
20
- - [Health Monitoring](#health-monitoring)
21
36
  - [Best Practices](#best-practices)
37
+ - [Health Monitoring](#health-monitoring)
22
38
  - [Troubleshooting](#troubleshooting)
39
+ - [Migration Guide](#migration-guide)
23
40
  - [Contributing](#contributing)
24
41
  - [License](#license)
25
42
 
@@ -37,6 +54,8 @@ NestJS Temporal Core bridges NestJS's powerful dependency injection system with
37
54
  - 📦 **Modular Architecture**: Separate modules for client, worker, activities, and schedules
38
55
  - 🔄 **Production Grade**: Connection pooling, graceful shutdown, and fault tolerance
39
56
 
57
+ [🔝 Back to top](#table-of-contents)
58
+
40
59
  ## Features
41
60
 
42
61
  - ✨ **Declarative Decorators**: `@Activity()` and `@ActivityMethod()` for clean activity definitions
@@ -48,6 +67,9 @@ NestJS Temporal Core bridges NestJS's powerful dependency injection system with
48
67
  - 📊 **Performance Monitoring**: Built-in metrics and performance tracking
49
68
  - 🔚 **Graceful Shutdown**: Clean resource cleanup and connection termination
50
69
  - 📦 **Modular Design**: Use only what you need (client-only, worker-only, etc.)
70
+ - 🔄 **Multiple Workers**: Support for multiple workers with different task queues
71
+
72
+ [🔝 Back to top](#table-of-contents)
51
73
 
52
74
  ## Installation
53
75
 
@@ -57,17 +79,17 @@ npm install nestjs-temporal-core @temporalio/client @temporalio/worker @temporal
57
79
 
58
80
  ### Peer Dependencies
59
81
 
60
- The package requires the following peer dependencies:
61
-
62
82
  ```bash
63
83
  npm install @nestjs/common @nestjs/core reflect-metadata rxjs
64
84
  ```
65
85
 
86
+ [🔝 Back to top](#table-of-contents)
87
+
66
88
  ## Quick Start
67
89
 
68
90
  ### 1. Enable Shutdown Hooks
69
91
 
70
- First, enable shutdown hooks in your `main.ts` for proper Temporal resource cleanup:
92
+ Enable shutdown hooks in your `main.ts` for proper Temporal resource cleanup:
71
93
 
72
94
  ```typescript
73
95
  // main.ts
@@ -76,10 +98,10 @@ import { AppModule } from './app.module';
76
98
 
77
99
  async function bootstrap() {
78
100
  const app = await NestFactory.create(AppModule);
79
-
101
+
80
102
  // Required for graceful Temporal connection cleanup
81
103
  app.enableShutdownHooks();
82
-
104
+
83
105
  await app.listen(3000);
84
106
  }
85
107
  bootstrap();
@@ -134,15 +156,15 @@ export interface PaymentData {
134
156
  @Injectable()
135
157
  @Activity({ name: 'payment-activities' })
136
158
  export class PaymentActivity {
137
-
159
+
138
160
  @ActivityMethod('processPayment')
139
161
  async processPayment(data: PaymentData): Promise<{ transactionId: string }> {
140
162
  // Payment processing logic with full NestJS DI support
141
163
  console.log(`Processing payment: $${data.amount} ${data.currency}`);
142
-
164
+
143
165
  // Simulate payment processing
144
166
  await new Promise(resolve => setTimeout(resolve, 1000));
145
-
167
+
146
168
  return { transactionId: `txn_${Date.now()}` };
147
169
  }
148
170
 
@@ -193,7 +215,7 @@ export async function processPaymentWorkflow(data: PaymentData): Promise<any> {
193
215
  const result = await processPayment(data);
194
216
  transactionId = result.transactionId;
195
217
  status = 'completed';
196
-
218
+
197
219
  return {
198
220
  success: true,
199
221
  transactionId,
@@ -201,12 +223,12 @@ export async function processPaymentWorkflow(data: PaymentData): Promise<any> {
201
223
  };
202
224
  } catch (error) {
203
225
  status = 'failed';
204
-
226
+
205
227
  // Compensate if needed
206
228
  if (transactionId) {
207
229
  await refundPayment(transactionId);
208
230
  }
209
-
231
+
210
232
  throw error;
211
233
  }
212
234
  }
@@ -230,7 +252,7 @@ export class PaymentService {
230
252
  const result = await this.temporal.startWorkflow(
231
253
  'processPaymentWorkflow',
232
254
  [paymentData],
233
- {
255
+ {
234
256
  workflowId: `payment-${Date.now()}`,
235
257
  taskQueue: 'my-task-queue',
236
258
  }
@@ -248,7 +270,7 @@ export class PaymentService {
248
270
  workflowId,
249
271
  'getPaymentStatus'
250
272
  );
251
-
273
+
252
274
  return { status: statusResult.result };
253
275
  }
254
276
 
@@ -263,6 +285,8 @@ export class PaymentService {
263
285
  }
264
286
  ```
265
287
 
288
+ [🔝 Back to top](#table-of-contents)
289
+
266
290
  ## Module Variants
267
291
 
268
292
  The package provides modular architecture with separate modules for different use cases:
@@ -338,7 +362,7 @@ TemporalSchedulesModule.register({
338
362
  })
339
363
  ```
340
364
 
341
- ## Configuration
365
+ [🔝 Back to top](#table-of-contents)
342
366
 
343
367
  ## Configuration
344
368
 
@@ -616,6 +640,8 @@ interface TemporalOptions {
616
640
  }
617
641
  ```
618
642
 
643
+ [🔝 Back to top](#table-of-contents)
644
+
619
645
  ## Core Concepts
620
646
 
621
647
  ### Activities
@@ -637,7 +663,7 @@ export class OrderActivity {
637
663
  private readonly orderRepository: OrderRepository,
638
664
  private readonly emailService: EmailService,
639
665
  ) {}
640
-
666
+
641
667
  @ActivityMethod('createOrder')
642
668
  async createOrder(orderData: CreateOrderData): Promise<Order> {
643
669
  // Database operations with full DI support
@@ -779,7 +805,7 @@ export class OrderService {
779
805
  workflowId,
780
806
  'getOrderStatus'
781
807
  );
782
-
808
+
783
809
  return result.result;
784
810
  }
785
811
 
@@ -793,417 +819,59 @@ export class OrderService {
793
819
  }
794
820
  ```
795
821
 
796
- ## API Reference
797
-
798
- ### TemporalService
799
-
800
- The main unified service providing access to all Temporal functionality:
801
-
802
- ```typescript
803
- class TemporalService {
804
- /**
805
- * Start a workflow execution
806
- * @param workflowType - Name of the workflow function
807
- * @param args - Array of arguments to pass to the workflow
808
- * @param options - Workflow execution options
809
- */
810
- async startWorkflow<T>(
811
- workflowType: string,
812
- args?: unknown[],
813
- options?: WorkflowStartOptions
814
- ): Promise<WorkflowExecutionResult<T>>
815
-
816
- /**
817
- * Send a signal to a running workflow
818
- * @param workflowId - The workflow ID
819
- * @param signalName - Name of the signal
820
- * @param args - Arguments for the signal
821
- */
822
- async signalWorkflow(
823
- workflowId: string,
824
- signalName: string,
825
- args?: unknown[]
826
- ): Promise<WorkflowSignalResult>
827
-
828
- /**
829
- * Query a running workflow
830
- * @param workflowId - The workflow ID
831
- * @param queryName - Name of the query
832
- * @param args - Arguments for the query
833
- */
834
- async queryWorkflow<T>(
835
- workflowId: string,
836
- queryName: string,
837
- args?: unknown[]
838
- ): Promise<WorkflowQueryResult<T>>
839
-
840
- /**
841
- * Get a workflow handle to interact with it
842
- * @param workflowId - The workflow ID
843
- * @param runId - Optional run ID for specific execution
844
- */
845
- async getWorkflowHandle<T>(
846
- workflowId: string,
847
- runId?: string
848
- ): Promise<T>
849
-
850
- /**
851
- * Terminate a workflow execution
852
- * @param workflowId - The workflow ID
853
- * @param reason - Termination reason
854
- */
855
- async terminateWorkflow(
856
- workflowId: string,
857
- reason?: string
858
- ): Promise<WorkflowTerminationResult>
859
-
860
- /**
861
- * Cancel a workflow execution
862
- * @param workflowId - The workflow ID
863
- */
864
- async cancelWorkflow(
865
- workflowId: string
866
- ): Promise<WorkflowCancellationResult>
867
-
868
- /**
869
- * Get service health status
870
- */
871
- getHealth(): ServiceHealth
872
-
873
- /**
874
- * Create a schedule
875
- */
876
- async createSchedule(options: ScheduleCreateOptions): Promise<ScheduleHandle>
877
-
878
- /**
879
- * List all schedules
880
- */
881
- async listSchedules(): Promise<ScheduleListDescription[]>
882
-
883
- /**
884
- * Delete a schedule
885
- */
886
- async deleteSchedule(scheduleId: string): Promise<void>
887
- }
888
- ```
889
-
890
- ### WorkflowStartOptions
891
-
892
- Options for starting workflows:
822
+ [🔝 Back to top](#table-of-contents)
893
823
 
894
- ```typescript
895
- interface WorkflowStartOptions {
896
- workflowId?: string; // Unique workflow ID
897
- taskQueue?: string; // Task queue name
898
- workflowExecutionTimeout?: Duration; // Total workflow timeout
899
- workflowRunTimeout?: Duration; // Single run timeout
900
- workflowTaskTimeout?: Duration; // Decision task timeout
901
- memo?: Record<string, unknown>; // Workflow memo
902
- searchAttributes?: SearchAttributes; // Search attributes for filtering
903
- }
904
- ```
824
+ ## API Reference
905
825
 
906
- ### Result Types
826
+ For detailed API documentation, visit the [Full API Documentation](https://harsh-simform.github.io/nestjs-temporal-core/).
907
827
 
908
- ```typescript
909
- interface WorkflowExecutionResult<T> {
910
- success: boolean;
911
- result: T; // Contains workflowId, runId, etc.
912
- executionTime: number;
913
- error?: Error;
914
- }
828
+ ### TemporalService
915
829
 
916
- interface WorkflowQueryResult<T> {
917
- success: boolean;
918
- result: T;
919
- workflowId: string;
920
- queryName: string;
921
- }
830
+ The main unified service providing access to all Temporal functionality. See the [API Documentation](https://harsh-simform.github.io/nestjs-temporal-core/) for complete method signatures and examples.
922
831
 
923
- interface WorkflowSignalResult {
924
- success: boolean;
925
- workflowId: string;
926
- signalName: string;
927
- }
928
- ```
832
+ Key methods:
833
+ - `startWorkflow()` - Start a workflow execution
834
+ - `signalWorkflow()` - Send a signal to a running workflow
835
+ - `queryWorkflow()` - Query a running workflow
836
+ - `getWorkflowHandle()` - Get a workflow handle to interact with it
837
+ - `terminateWorkflow()` - Terminate a workflow execution
838
+ - `cancelWorkflow()` - Cancel a workflow execution
839
+ - `getHealth()` - Get service health status
840
+ - `createSchedule()` - Create a schedule
841
+ - `listSchedules()` - List all schedules
842
+ - `deleteSchedule()` - Delete a schedule
929
843
 
930
- ## Examples
844
+ [🔝 Back to top](#table-of-contents)
931
845
 
932
846
  ## Examples
933
847
 
934
- ### Example 1: E-commerce Order Processing
935
-
936
- Complete example with compensation logic:
937
-
938
- ```typescript
939
- // order.activity.ts
940
- @Injectable()
941
- @Activity({ name: 'order-activities' })
942
- export class OrderActivity {
943
- constructor(
944
- private readonly paymentService: PaymentService,
945
- private readonly inventoryService: InventoryService,
946
- private readonly emailService: EmailService,
947
- ) {}
948
-
949
- @ActivityMethod('validatePayment')
950
- async validatePayment(paymentData: PaymentData): Promise<PaymentResult> {
951
- return await this.paymentService.validate(paymentData);
952
- }
953
-
954
- @ActivityMethod('chargePayment')
955
- async chargePayment(paymentData: PaymentData): Promise<{ transactionId: string }> {
956
- return await this.paymentService.charge(paymentData);
957
- }
958
-
959
- @ActivityMethod('refundPayment')
960
- async refundPayment(transactionId: string): Promise<void> {
961
- await this.paymentService.refund(transactionId);
962
- }
963
-
964
- @ActivityMethod('reserveInventory')
965
- async reserveInventory(items: OrderItem[]): Promise<{ reservationId: string }> {
966
- return await this.inventoryService.reserve(items);
967
- }
968
-
969
- @ActivityMethod('releaseInventory')
970
- async releaseInventory(reservationId: string): Promise<void> {
971
- await this.inventoryService.release(reservationId);
972
- }
973
-
974
- @ActivityMethod('sendConfirmationEmail')
975
- async sendConfirmationEmail(order: Order): Promise<void> {
976
- await this.emailService.sendConfirmation(order);
977
- }
978
- }
979
-
980
- // order.workflow.ts
981
- import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
982
- import type { OrderActivity } from './order.activity';
983
-
984
- const {
985
- validatePayment,
986
- chargePayment,
987
- refundPayment,
988
- reserveInventory,
989
- releaseInventory,
990
- sendConfirmationEmail,
991
- } = proxyActivities<typeof OrderActivity.prototype>({
992
- startToCloseTimeout: '5m',
993
- retry: { maximumAttempts: 3 },
994
- });
995
-
996
- export const cancelOrderSignal = defineSignal<[string]>('cancelOrder');
997
- export const getOrderStatusQuery = defineQuery<OrderStatus>('getOrderStatus');
998
-
999
- export async function processOrderWorkflow(orderData: OrderData): Promise<OrderResult> {
1000
- let status: OrderStatus = 'pending';
1001
- let transactionId: string | undefined;
1002
- let reservationId: string | undefined;
1003
- let cancelled = false;
1004
-
1005
- setHandler(cancelOrderSignal, (reason: string) => {
1006
- cancelled = true;
1007
- });
1008
-
1009
- setHandler(getOrderStatusQuery, () => status);
1010
-
1011
- try {
1012
- // Step 1: Validate payment
1013
- status = 'validating_payment';
1014
- const paymentValid = await validatePayment(orderData.payment);
1015
- if (!paymentValid.valid) {
1016
- throw new Error('Invalid payment method');
1017
- }
1018
-
1019
- // Check cancellation
1020
- if (cancelled) {
1021
- status = 'cancelled';
1022
- return { orderId: orderData.orderId, status };
1023
- }
1024
-
1025
- // Step 2: Reserve inventory
1026
- status = 'reserving_inventory';
1027
- const reservation = await reserveInventory(orderData.items);
1028
- reservationId = reservation.reservationId;
1029
-
1030
- // Step 3: Charge payment
1031
- status = 'charging_payment';
1032
- const payment = await chargePayment(orderData.payment);
1033
- transactionId = payment.transactionId;
1034
-
1035
- // Step 4: Send confirmation
1036
- status = 'sending_confirmation';
1037
- await sendConfirmationEmail({
1038
- orderId: orderData.orderId,
1039
- items: orderData.items,
1040
- total: orderData.totalAmount,
1041
- });
1042
-
1043
- status = 'completed';
1044
- return {
1045
- orderId: orderData.orderId,
1046
- status,
1047
- transactionId,
1048
- reservationId,
1049
- };
1050
- } catch (error) {
1051
- // Compensation logic
1052
- status = 'compensating';
1053
-
1054
- if (reservationId) {
1055
- await releaseInventory(reservationId);
1056
- }
848
+ ### 🚀 Example Project
1057
849
 
1058
- if (transactionId) {
1059
- await refundPayment(transactionId);
1060
- }
850
+ Check out our **[complete example repository](https://github.com/harsh-simform/nestjs-temporal-core-example)** featuring:
1061
851
 
1062
- status = 'failed';
1063
- throw error;
1064
- }
1065
- }
852
+ - **Real-world implementations** - Production-ready examples
853
+ - ✅ **Multiple use cases** - E-commerce, notifications, reports, and more
854
+ - ✅ **Best practices** - Following all recommended patterns
855
+ - ✅ **Docker setup** - Ready-to-run with docker-compose
856
+ - ✅ **Test coverage** - Comprehensive test examples
1066
857
 
1067
- // order.service.ts
1068
- @Injectable()
1069
- export class OrderService {
1070
- constructor(private readonly temporal: TemporalService) {}
858
+ ### 📚 Documentation Examples
1071
859
 
1072
- async createOrder(orderData: OrderData) {
1073
- const result = await this.temporal.startWorkflow(
1074
- 'processOrderWorkflow',
1075
- [orderData],
1076
- {
1077
- workflowId: `order-${orderData.orderId}`,
1078
- taskQueue: 'order-queue',
1079
- }
1080
- );
860
+ For more examples, visit our [documentation](https://harsh-simform.github.io/nestjs-temporal-core/). Key example scenarios include:
1081
861
 
1082
- return result.result;
1083
- }
862
+ 1. **E-commerce Order Processing** - Complete example with compensation logic
863
+ 2. **Scheduled Reports** - Creating and managing scheduled workflows
864
+ 3. **Activity Retry Configuration** - Custom retry policies
865
+ 4. **Child Workflows** - Organizing complex workflows
866
+ 5. **Continue-As-New** - For long-running workflows
867
+ 6. **Custom Error Handling** - Implementing custom error types
1084
868
 
1085
- async getOrderStatus(orderId: string) {
1086
- const result = await this.temporal.queryWorkflow(
1087
- `order-${orderId}`,
1088
- 'getOrderStatus'
1089
- );
1090
-
1091
- return result.result;
1092
- }
1093
-
1094
- async cancelOrder(orderId: string, reason: string) {
1095
- await this.temporal.signalWorkflow(
1096
- `order-${orderId}`,
1097
- 'cancelOrder',
1098
- [reason]
1099
- );
1100
- }
1101
- }
1102
- ```
1103
-
1104
- ### Example 2: Scheduled Reports
1105
-
1106
- Creating and managing scheduled workflows:
1107
-
1108
- ```typescript
1109
- // report.activity.ts
1110
- @Injectable()
1111
- @Activity({ name: 'report-activities' })
1112
- export class ReportActivity {
1113
- constructor(
1114
- private readonly reportService: ReportService,
1115
- private readonly storageService: StorageService,
1116
- private readonly notificationService: NotificationService,
1117
- ) {}
1118
-
1119
- @ActivityMethod('generateSalesReport')
1120
- async generateSalesReport(period: ReportPeriod): Promise<ReportData> {
1121
- return await this.reportService.generateSales(period);
1122
- }
1123
-
1124
- @ActivityMethod('uploadReport')
1125
- async uploadReport(reportData: ReportData): Promise<string> {
1126
- return await this.storageService.upload(reportData);
1127
- }
1128
-
1129
- @ActivityMethod('notifyStakeholders')
1130
- async notifyStakeholders(reportUrl: string, recipients: string[]): Promise<void> {
1131
- await this.notificationService.send(recipients, reportUrl);
1132
- }
1133
- }
1134
-
1135
- // report.workflow.ts
1136
- import { proxyActivities } from '@temporalio/workflow';
1137
- import type { ReportActivity } from './report.activity';
1138
-
1139
- const { generateSalesReport, uploadReport, notifyStakeholders } =
1140
- proxyActivities<typeof ReportActivity.prototype>({
1141
- startToCloseTimeout: '10m',
1142
- });
1143
-
1144
- export async function weeklyReportWorkflow(): Promise<ReportResult> {
1145
- const endDate = new Date();
1146
- const startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
1147
-
1148
- // Generate report
1149
- const reportData = await generateSalesReport({
1150
- startDate,
1151
- endDate,
1152
- type: 'weekly',
1153
- });
1154
-
1155
- // Upload to storage
1156
- const reportUrl = await uploadReport(reportData);
1157
-
1158
- // Notify stakeholders
1159
- await notifyStakeholders(reportUrl, ['management@company.com']);
1160
-
1161
- return {
1162
- reportUrl,
1163
- generatedAt: new Date(),
1164
- period: { startDate, endDate },
1165
- };
1166
- }
1167
-
1168
- // schedule.service.ts
1169
- @Injectable()
1170
- export class ReportScheduleService {
1171
- constructor(private readonly temporal: TemporalService) {}
1172
-
1173
- async setupWeeklyReports() {
1174
- await this.temporal.createSchedule({
1175
- scheduleId: 'weekly-sales-report',
1176
- spec: {
1177
- cronExpressions: ['0 9 * * MON'], // Every Monday at 9 AM
1178
- },
1179
- action: {
1180
- type: 'startWorkflow',
1181
- workflowType: 'weeklyReportWorkflow',
1182
- taskQueue: 'reports-queue',
1183
- },
1184
- policies: {
1185
- overlap: 'SKIP',
1186
- catchupWindow: '1 hour',
1187
- },
1188
- });
1189
- }
1190
-
1191
- async deleteSchedule(scheduleId: string) {
1192
- await this.temporal.deleteSchedule(scheduleId);
1193
- }
1194
-
1195
- async listAllSchedules() {
1196
- return await this.temporal.listSchedules();
1197
- }
1198
- }
1199
- ```
869
+ [🔝 Back to top](#table-of-contents)
1200
870
 
1201
871
  ## Advanced Usage
1202
872
 
1203
873
  ### Activity Retry Configuration
1204
874
 
1205
- Configure custom retry policies for different activity types:
1206
-
1207
875
  ```typescript
1208
876
  // workflow.ts
1209
877
  const paymentActivities = proxyActivities<typeof PaymentActivity.prototype>({
@@ -1216,25 +884,14 @@ const paymentActivities = proxyActivities<typeof PaymentActivity.prototype>({
1216
884
  nonRetryableErrorTypes: ['InvalidPaymentMethod', 'InsufficientFunds'],
1217
885
  },
1218
886
  });
1219
-
1220
- const emailActivities = proxyActivities<typeof EmailActivity.prototype>({
1221
- startToCloseTimeout: '2m',
1222
- retry: {
1223
- maximumAttempts: 3,
1224
- initialInterval: '500ms',
1225
- },
1226
- });
1227
887
  ```
1228
888
 
1229
889
  ### Workflow Testing
1230
890
 
1231
- Test workflows using Temporal's testing framework:
1232
-
1233
891
  ```typescript
1234
892
  import { TestWorkflowEnvironment } from '@temporalio/testing';
1235
893
  import { Worker } from '@temporalio/worker';
1236
894
  import { processOrderWorkflow } from './order.workflow';
1237
- import { OrderActivity } from './order.activity';
1238
895
 
1239
896
  describe('Order Workflow', () => {
1240
897
  let testEnv: TestWorkflowEnvironment;
@@ -1283,102 +940,7 @@ describe('Order Workflow', () => {
1283
940
  });
1284
941
  ```
1285
942
 
1286
- ### Child Workflows
1287
-
1288
- Organize complex workflows using child workflows:
1289
-
1290
- ```typescript
1291
- // parent.workflow.ts
1292
- import { startChild } from '@temporalio/workflow';
1293
-
1294
- export async function parentWorkflow(orderId: string) {
1295
- // Start child workflows
1296
- const paymentHandle = await startChild(processPaymentWorkflow, {
1297
- workflowId: `payment-${orderId}`,
1298
- args: [paymentData],
1299
- });
1300
-
1301
- const shippingHandle = await startChild(processShippingWorkflow, {
1302
- workflowId: `shipping-${orderId}`,
1303
- args: [shippingData],
1304
- });
1305
-
1306
- // Wait for both to complete
1307
- const [paymentResult, shippingResult] = await Promise.all([
1308
- paymentHandle.result(),
1309
- shippingHandle.result(),
1310
- ]);
1311
-
1312
- return {
1313
- payment: paymentResult,
1314
- shipping: shippingResult,
1315
- };
1316
- }
1317
- ```
1318
-
1319
- ### Continue-As-New for Long-Running Workflows
1320
-
1321
- Use continue-as-new to prevent event history from growing too large:
1322
-
1323
- ```typescript
1324
- import { continueAsNew } from '@temporalio/workflow';
1325
-
1326
- export async function processEventStreamWorkflow(cursor: number): Promise<void> {
1327
- const events = await fetchEvents(cursor);
1328
-
1329
- for (const event of events) {
1330
- await processEvent(event);
1331
- }
1332
-
1333
- // Continue as new after processing 1000 events
1334
- if (events.length >= 1000) {
1335
- await continueAsNew<typeof processEventStreamWorkflow>(cursor + events.length);
1336
- }
1337
- }
1338
- ```
1339
-
1340
- ### Custom Error Handling
1341
-
1342
- Implement custom error types and handling:
1343
-
1344
- ```typescript
1345
- // activities
1346
- export class RetryableError extends Error {
1347
- constructor(message: string) {
1348
- super(message);
1349
- this.name = 'RetryableError';
1350
- }
1351
- }
1352
-
1353
- export class NonRetryableError extends Error {
1354
- constructor(message: string) {
1355
- super(message);
1356
- this.name = 'NonRetryableError';
1357
- }
1358
- }
1359
-
1360
- @ActivityMethod('processData')
1361
- async processData(data: any): Promise<any> {
1362
- try {
1363
- return await this.externalApi.process(data);
1364
- } catch (error) {
1365
- if (error.code === 'RATE_LIMIT') {
1366
- throw new RetryableError('Rate limit exceeded, will retry');
1367
- } else if (error.code === 'INVALID_DATA') {
1368
- throw new NonRetryableError('Invalid data format');
1369
- }
1370
- throw error;
1371
- }
1372
- }
1373
-
1374
- // workflow configuration
1375
- const activities = proxyActivities<typeof DataActivity.prototype>({
1376
- startToCloseTimeout: '5m',
1377
- retry: {
1378
- nonRetryableErrorTypes: ['NonRetryableError'],
1379
- },
1380
- });
1381
- ```
943
+ [🔝 Back to top](#table-of-contents)
1382
944
 
1383
945
  ## Best Practices
1384
946
 
@@ -1453,6 +1015,8 @@ const activities = proxyActivities<typeof DataActivity.prototype>({
1453
1015
  - Don't test against production Temporal server
1454
1016
  - Don't assume workflows are correct without testing
1455
1017
 
1018
+ [🔝 Back to top](#table-of-contents)
1019
+
1456
1020
  ## Health Monitoring
1457
1021
 
1458
1022
  The package includes comprehensive health monitoring capabilities for production deployments.
@@ -1491,7 +1055,7 @@ export class HealthController {
1491
1055
  @Get('/status')
1492
1056
  async getHealthStatus() {
1493
1057
  const health = this.temporal.getHealth();
1494
-
1058
+
1495
1059
  return {
1496
1060
  status: health.overallHealth,
1497
1061
  timestamp: new Date(),
@@ -1513,249 +1077,10 @@ export class HealthController {
1513
1077
  uptime: health.uptime,
1514
1078
  };
1515
1079
  }
1516
-
1517
- @Get('/detailed')
1518
- async getDetailedHealth() {
1519
- const health = this.temporal.getHealth();
1520
- const stats = this.temporal.getStatistics();
1521
-
1522
- return {
1523
- health,
1524
- statistics: stats,
1525
- performance: {
1526
- workflowStartLatency: stats.averageWorkflowStartTime,
1527
- activityExecutionCount: stats.totalActivitiesExecuted,
1528
- },
1529
- };
1530
- }
1531
- }
1532
- ```
1533
-
1534
- ### Health Check Response
1535
-
1536
- ```typescript
1537
- interface ServiceHealth {
1538
- overallHealth: 'healthy' | 'degraded' | 'unhealthy';
1539
- client: {
1540
- status: 'healthy' | 'unhealthy';
1541
- connectionStatus: 'connected' | 'disconnected';
1542
- };
1543
- worker: {
1544
- status: 'healthy' | 'unhealthy';
1545
- state: 'RUNNING' | 'STOPPED' | 'FAILED';
1546
- activitiesCount: number;
1547
- };
1548
- discovery: {
1549
- status: 'healthy' | 'unhealthy';
1550
- activitiesDiscovered: number;
1551
- };
1552
- uptime: number;
1553
- lastChecked: Date;
1554
1080
  }
1555
1081
  ```
1556
- ```
1557
1082
 
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
- ```
1083
+ [🔝 Back to top](#table-of-contents)
1759
1084
 
1760
1085
  ## Troubleshooting
1761
1086
 
@@ -1765,10 +1090,6 @@ export class WorkerHealthService {
1765
1090
 
1766
1091
  **Problem:** Cannot connect to Temporal server
1767
1092
 
1768
- ```
1769
- Error: Failed to connect to localhost:7233
1770
- ```
1771
-
1772
1093
  **Solutions:**
1773
1094
  ```typescript
1774
1095
  // Check connection configuration
@@ -1791,10 +1112,6 @@ TemporalModule.register({
1791
1112
 
1792
1113
  **Problem:** Workflow cannot find registered activities
1793
1114
 
1794
- ```
1795
- Error: Activity 'myActivity' not found
1796
- ```
1797
-
1798
1115
  **Solutions:**
1799
1116
  ```typescript
1800
1117
  // 1. Ensure activity is in activityClasses array
@@ -1825,10 +1142,6 @@ console.log('Activities discovered:', health.discovery.activitiesDiscovered);
1825
1142
 
1826
1143
  **Problem:** Workflow not found or not executing
1827
1144
 
1828
- ```
1829
- Error: Workflow 'myWorkflow' not found
1830
- ```
1831
-
1832
1145
  **Solutions:**
1833
1146
  ```typescript
1834
1147
  // 1. Ensure workflowsPath is correct
@@ -1855,10 +1168,6 @@ await temporal.startWorkflow(
1855
1168
 
1856
1169
  **Problem:** Activities or workflows timing out
1857
1170
 
1858
- ```
1859
- Error: Activity timed out after 10s
1860
- ```
1861
-
1862
1171
  **Solutions:**
1863
1172
  ```typescript
1864
1173
  // Configure appropriate timeouts
@@ -1876,67 +1185,6 @@ await temporal.startWorkflow('myWorkflow', [args], {
1876
1185
  });
1877
1186
  ```
1878
1187
 
1879
- #### 5. Worker Not Starting
1880
-
1881
- **Problem:** Worker fails to start or crashes
1882
-
1883
- ```
1884
- Error: Worker failed to start
1885
- ```
1886
-
1887
- **Solutions:**
1888
- ```typescript
1889
- // 1. Check worker configuration
1890
- TemporalModule.register({
1891
- worker: {
1892
- autoStart: true, // Ensure autoStart is true
1893
- workflowsPath: require.resolve('./workflows'),
1894
- activityClasses: [MyActivity],
1895
- },
1896
- })
1897
-
1898
- // 2. Check logs
1899
- // Enable debug logging
1900
- TemporalModule.register({
1901
- logLevel: 'debug',
1902
- enableLogger: true,
1903
- })
1904
-
1905
- // 3. Verify worker health
1906
- const health = temporalService.getHealth();
1907
- console.log('Worker status:', health.worker.state);
1908
-
1909
- // 4. Check for port conflicts or resource issues
1910
- ```
1911
-
1912
- #### 6. Signal/Query Not Working
1913
-
1914
- **Problem:** Signals or queries not being handled
1915
-
1916
- **Solutions:**
1917
- ```typescript
1918
- // 1. Define signals/queries at module level (not inside workflow)
1919
- export const mySignal = defineSignal<[string]>('mySignal');
1920
- export const myQuery = defineQuery<string>('myQuery');
1921
-
1922
- // 2. Set up handlers in workflow
1923
- export async function myWorkflow() {
1924
- let value = 'initial';
1925
-
1926
- setHandler(mySignal, (newValue: string) => {
1927
- value = newValue;
1928
- });
1929
-
1930
- setHandler(myQuery, () => value);
1931
-
1932
- // ... workflow logic
1933
- }
1934
-
1935
- // 3. Use correct names when signaling/querying
1936
- await temporal.signalWorkflow(workflowId, 'mySignal', ['newValue']);
1937
- const result = await temporal.queryWorkflow(workflowId, 'myQuery');
1938
- ```
1939
-
1940
1188
  ### Debug Mode
1941
1189
 
1942
1190
  Enable comprehensive debugging:
@@ -1971,10 +1219,85 @@ If you're still experiencing issues:
1971
1219
  5. **Check GitHub Issues** - [Search existing issues](https://github.com/harsh-simform/nestjs-temporal-core/issues)
1972
1220
  6. **Create an issue** - Provide logs, configuration, and minimal reproduction
1973
1221
 
1222
+ [🔝 Back to top](#table-of-contents)
1223
+
1224
+ ## Migration Guide
1225
+
1226
+ ### Migrating to v3.0.12+ (Multiple Workers Support)
1227
+
1228
+ Version 3.0.12 introduces support for multiple workers without breaking existing single-worker configurations.
1229
+
1230
+ #### No Changes Required for Single Worker
1231
+
1232
+ Your existing configuration continues to work:
1233
+
1234
+ ```typescript
1235
+ // ✅ This still works exactly as before
1236
+ TemporalModule.register({
1237
+ connection: { address: 'localhost:7233' },
1238
+ taskQueue: 'my-queue',
1239
+ worker: {
1240
+ workflowsPath: require.resolve('./workflows'),
1241
+ activityClasses: [MyActivity],
1242
+ },
1243
+ })
1244
+ ```
1245
+
1246
+ #### Migrating to Multiple Workers
1247
+
1248
+ **After (v3.0.12):**
1249
+ ```typescript
1250
+ // Option 1: Configure multiple workers in module
1251
+ TemporalModule.register({
1252
+ connection: { address: 'localhost:7233' },
1253
+ workers: [
1254
+ {
1255
+ taskQueue: 'main-queue',
1256
+ workflowsPath: require.resolve('./workflows/main'),
1257
+ activityClasses: [MainActivity],
1258
+ },
1259
+ {
1260
+ taskQueue: 'schedule-queue',
1261
+ workflowsPath: require.resolve('./workflows/schedule'),
1262
+ activityClasses: [ScheduleActivity],
1263
+ },
1264
+ ],
1265
+ })
1266
+ ```
1267
+
1268
+ #### New APIs in v3.0.12
1269
+
1270
+ ```typescript
1271
+ // Get native connection for custom worker creation
1272
+ const workerManager = temporal.getWorkerManager();
1273
+ const connection: NativeConnection | null = workerManager.getConnection();
1274
+
1275
+ // Get specific worker by task queue
1276
+ const worker: Worker | null = temporal.getWorker('payments-queue');
1277
+
1278
+ // Get all workers information
1279
+ const workersInfo: MultipleWorkersInfo = temporal.getAllWorkers();
1280
+ console.log(`${workersInfo.runningWorkers}/${workersInfo.totalWorkers} workers running`);
1281
+
1282
+ // Control specific workers
1283
+ await temporal.startWorkerByTaskQueue('payments-queue');
1284
+ await temporal.stopWorkerByTaskQueue('notifications-queue');
1285
+
1286
+ // Register new worker dynamically
1287
+ const result = await temporal.registerWorker({
1288
+ taskQueue: 'new-queue',
1289
+ workflowsPath: require.resolve('./workflows/new'),
1290
+ activityClasses: [NewActivity],
1291
+ autoStart: true,
1292
+ });
1293
+ ```
1294
+
1295
+ [🔝 Back to top](#table-of-contents)
1296
+
1974
1297
  ## Requirements
1975
1298
 
1976
1299
  - **Node.js**: >= 16.0.0
1977
- - **NestJS**: >= 9.0.0
1300
+ - **NestJS**: >= 9.0.0
1978
1301
  - **Temporal Server**: >= 1.20.0
1979
1302
 
1980
1303
  ## Contributing
@@ -1989,8 +1312,6 @@ We welcome contributions! To contribute:
1989
1312
  6. Push to the branch (`git push origin feature/amazing-feature`)
1990
1313
  7. Open a Pull Request
1991
1314
 
1992
- Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
1993
-
1994
1315
  ### Development Setup
1995
1316
 
1996
1317
  ```bash
@@ -2014,6 +1335,8 @@ npm run build
2014
1335
  npm run docs:generate
2015
1336
  ```
2016
1337
 
1338
+ [🔝 Back to top](#table-of-contents)
1339
+
2017
1340
  ## License
2018
1341
 
2019
1342
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -2035,3 +1358,10 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
2035
1358
 
2036
1359
  ---
2037
1360
 
1361
+ <div align="center">
1362
+
1363
+ **[⭐ Star us on GitHub](https://github.com/harsh-simform/nestjs-temporal-core)** if you find this project helpful!
1364
+
1365
+ Made with ❤️ by the Harsh Simform
1366
+
1367
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-temporal-core",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Complete NestJS integration for Temporal.io with auto-discovery, declarative scheduling, enhanced monitoring, and enterprise-ready features",
5
5
  "author": "Harsh M",
6
6
  "license": "MIT",