nestjs-temporal-core 3.0.10 → 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.
Files changed (123) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1755 -693
  3. package/dist/constants.d.ts +49 -151
  4. package/dist/constants.js +38 -141
  5. package/dist/constants.js.map +1 -1
  6. package/dist/decorators/activity.decorator.js +75 -15
  7. package/dist/decorators/activity.decorator.js.map +1 -1
  8. package/dist/decorators/index.d.ts +1 -3
  9. package/dist/decorators/index.js +4 -13
  10. package/dist/decorators/index.js.map +1 -1
  11. package/dist/decorators/workflow.decorator.d.ts +7 -3
  12. package/dist/decorators/workflow.decorator.js +161 -48
  13. package/dist/decorators/workflow.decorator.js.map +1 -1
  14. package/dist/health/temporal-health.controller.d.ts +7 -0
  15. package/dist/health/temporal-health.controller.js +77 -0
  16. package/dist/health/temporal-health.controller.js.map +1 -0
  17. package/dist/health/temporal-health.module.d.ts +2 -0
  18. package/dist/health/temporal-health.module.js +20 -0
  19. package/dist/health/temporal-health.module.js.map +1 -0
  20. package/dist/index.d.ts +10 -20
  21. package/dist/index.js +15 -30
  22. package/dist/index.js.map +1 -1
  23. package/dist/interfaces.d.ts +773 -160
  24. package/dist/interfaces.js +1 -2
  25. package/dist/interfaces.js.map +1 -1
  26. package/dist/providers/temporal-connection.factory.d.ts +28 -0
  27. package/dist/providers/temporal-connection.factory.js +194 -0
  28. package/dist/providers/temporal-connection.factory.js.map +1 -0
  29. package/dist/services/temporal-client.service.d.ts +33 -0
  30. package/dist/services/temporal-client.service.js +285 -0
  31. package/dist/services/temporal-client.service.js.map +1 -0
  32. package/dist/services/temporal-discovery.service.d.ts +34 -0
  33. package/dist/services/temporal-discovery.service.js +348 -0
  34. package/dist/services/temporal-discovery.service.js.map +1 -0
  35. package/dist/services/temporal-metadata.service.d.ts +37 -0
  36. package/dist/services/temporal-metadata.service.js +512 -0
  37. package/dist/services/temporal-metadata.service.js.map +1 -0
  38. package/dist/services/temporal-schedule.service.d.ts +35 -0
  39. package/dist/services/temporal-schedule.service.js +380 -0
  40. package/dist/services/temporal-schedule.service.js.map +1 -0
  41. package/dist/services/temporal-worker.service.d.ts +67 -0
  42. package/dist/services/temporal-worker.service.js +845 -0
  43. package/dist/services/temporal-worker.service.js.map +1 -0
  44. package/dist/services/temporal.service.d.ts +92 -0
  45. package/dist/services/temporal.service.js +621 -0
  46. package/dist/services/temporal.service.js.map +1 -0
  47. package/dist/temporal.module.d.ts +6 -9
  48. package/dist/temporal.module.js +160 -109
  49. package/dist/temporal.module.js.map +1 -1
  50. package/dist/tsconfig.tsbuildinfo +1 -1
  51. package/dist/utils/index.d.ts +2 -2
  52. package/dist/utils/index.js +5 -8
  53. package/dist/utils/index.js.map +1 -1
  54. package/dist/utils/logger.d.ts +10 -4
  55. package/dist/utils/logger.js +77 -106
  56. package/dist/utils/logger.js.map +1 -1
  57. package/dist/utils/metadata.d.ts +1 -3
  58. package/dist/utils/metadata.js +8 -18
  59. package/dist/utils/metadata.js.map +1 -1
  60. package/dist/utils/validation.d.ts +16 -2
  61. package/dist/utils/validation.js +103 -9
  62. package/dist/utils/validation.js.map +1 -1
  63. package/package.json +37 -26
  64. package/dist/activity/index.d.ts +0 -2
  65. package/dist/activity/index.js +0 -19
  66. package/dist/activity/index.js.map +0 -1
  67. package/dist/activity/temporal-activity.module.d.ts +0 -11
  68. package/dist/activity/temporal-activity.module.js +0 -52
  69. package/dist/activity/temporal-activity.module.js.map +0 -1
  70. package/dist/activity/temporal-activity.service.d.ts +0 -46
  71. package/dist/activity/temporal-activity.service.js +0 -192
  72. package/dist/activity/temporal-activity.service.js.map +0 -1
  73. package/dist/client/index.d.ts +0 -3
  74. package/dist/client/index.js +0 -20
  75. package/dist/client/index.js.map +0 -1
  76. package/dist/client/temporal-client.module.d.ts +0 -18
  77. package/dist/client/temporal-client.module.js +0 -198
  78. package/dist/client/temporal-client.module.js.map +0 -1
  79. package/dist/client/temporal-client.service.d.ts +0 -35
  80. package/dist/client/temporal-client.service.js +0 -187
  81. package/dist/client/temporal-client.service.js.map +0 -1
  82. package/dist/client/temporal-schedule.service.d.ts +0 -41
  83. package/dist/client/temporal-schedule.service.js +0 -204
  84. package/dist/client/temporal-schedule.service.js.map +0 -1
  85. package/dist/decorators/parameter.decorator.d.ts +0 -5
  86. package/dist/decorators/parameter.decorator.js +0 -57
  87. package/dist/decorators/parameter.decorator.js.map +0 -1
  88. package/dist/decorators/scheduling.decorator.d.ts +0 -4
  89. package/dist/decorators/scheduling.decorator.js +0 -44
  90. package/dist/decorators/scheduling.decorator.js.map +0 -1
  91. package/dist/discovery/index.d.ts +0 -2
  92. package/dist/discovery/index.js +0 -19
  93. package/dist/discovery/index.js.map +0 -1
  94. package/dist/discovery/temporal-discovery.service.d.ts +0 -39
  95. package/dist/discovery/temporal-discovery.service.js +0 -191
  96. package/dist/discovery/temporal-discovery.service.js.map +0 -1
  97. package/dist/discovery/temporal-schedule-manager.service.d.ts +0 -41
  98. package/dist/discovery/temporal-schedule-manager.service.js +0 -238
  99. package/dist/discovery/temporal-schedule-manager.service.js.map +0 -1
  100. package/dist/schedules/index.d.ts +0 -2
  101. package/dist/schedules/index.js +0 -19
  102. package/dist/schedules/index.js.map +0 -1
  103. package/dist/schedules/temporal-schedules.module.d.ts +0 -11
  104. package/dist/schedules/temporal-schedules.module.js +0 -55
  105. package/dist/schedules/temporal-schedules.module.js.map +0 -1
  106. package/dist/schedules/temporal-schedules.service.d.ts +0 -52
  107. package/dist/schedules/temporal-schedules.service.js +0 -221
  108. package/dist/schedules/temporal-schedules.service.js.map +0 -1
  109. package/dist/temporal.service.d.ts +0 -77
  110. package/dist/temporal.service.js +0 -243
  111. package/dist/temporal.service.js.map +0 -1
  112. package/dist/worker/index.d.ts +0 -3
  113. package/dist/worker/index.js +0 -20
  114. package/dist/worker/index.js.map +0 -1
  115. package/dist/worker/temporal-metadata.accessor.d.ts +0 -32
  116. package/dist/worker/temporal-metadata.accessor.js +0 -208
  117. package/dist/worker/temporal-metadata.accessor.js.map +0 -1
  118. package/dist/worker/temporal-worker-manager.service.d.ts +0 -49
  119. package/dist/worker/temporal-worker-manager.service.js +0 -389
  120. package/dist/worker/temporal-worker-manager.service.js.map +0 -1
  121. package/dist/worker/temporal-worker.module.d.ts +0 -18
  122. package/dist/worker/temporal-worker.module.js +0 -156
  123. package/dist/worker/temporal-worker.module.js.map +0 -1
package/README.md CHANGED
@@ -1,85 +1,100 @@
1
1
  # NestJS Temporal Core
2
2
 
3
- A comprehensive NestJS integration for [Temporal.io](https://temporal.io/) that provides seamless workflow orchestration with auto-discovery, declarative scheduling, and production-ready features.
3
+ A comprehensive NestJS integration framework for Temporal.io that provides enterprise-ready workflow orchestration with automatic discovery, declarative decorators, and robust monitoring capabilities.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/nestjs-temporal-core.svg)](https://badge.fury.io/js/nestjs-temporal-core)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
8
7
 
9
- ## Overview
8
+ ## Table of Contents
9
+
10
+ - [Overview](#overview)
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Module Variants](#module-variants)
15
+ - [Configuration](#configuration)
16
+ - [Core Concepts](#core-concepts)
17
+ - [API Reference](#api-reference)
18
+ - [Examples](#examples)
19
+ - [Advanced Usage](#advanced-usage)
20
+ - [Health Monitoring](#health-monitoring)
21
+ - [Best Practices](#best-practices)
22
+ - [Troubleshooting](#troubleshooting)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
10
25
 
11
- NestJS Temporal Core brings Temporal's durable execution to NestJS with familiar decorator patterns and automatic discovery. Build reliable distributed systems with activities and scheduled tasks using native NestJS conventions.
26
+ ## Overview
12
27
 
13
- ## 💡 Example Repository
28
+ NestJS Temporal Core bridges NestJS's powerful dependency injection system with Temporal.io's robust workflow orchestration engine. It provides a declarative approach to building distributed, fault-tolerant applications with automatic service discovery, enterprise-grade monitoring, and seamless integration.
14
29
 
15
- 🔗 **[Complete Example Project](https://github.com/harsh-simform/nestjs-temporal-core-example)** - Check out our full working example repository to see NestJS Temporal Core in action with real-world use cases, configuration examples, and best practices.
30
+ ### Key Benefits
16
31
 
17
- ## 🏁 Getting Started: Recommendations
32
+ - 🚀 **Seamless Integration**: Native NestJS decorators and dependency injection
33
+ - 🔍 **Auto-Discovery**: Automatic registration of activities via decorators
34
+ - 🛡️ **Type Safety**: Full TypeScript support with comprehensive type definitions
35
+ - 🏥 **Enterprise Ready**: Built-in health checks, monitoring, and error handling
36
+ - ⚙️ **Zero Configuration**: Smart defaults with extensive customization options
37
+ - 📦 **Modular Architecture**: Separate modules for client, worker, activities, and schedules
38
+ - 🔄 **Production Grade**: Connection pooling, graceful shutdown, and fault tolerance
18
39
 
19
- Welcome to NestJS Temporal Core! Here are some quick recommendations to help you get started smoothly:
40
+ ## Features
20
41
 
21
- - **Choose Your Workflow Style:** You can implement workflows as plain exported functions (recommended for most use cases) or as injectable classes with decorators for advanced scenarios. See the comparison below.
22
- - **Parameter Injection:** Use `@WorkflowParam`, `@WorkflowId`, etc., only if you need advanced injection or metadata. For most workflows, plain parameters are simpler and preferred.
23
- - **Scheduling:** Schedules trigger workflows, not service methods. Make sure your scheduled workflow is exported and available to the worker.
24
- - **Signals & Queries:** Use signals to update workflow state and queries to fetch workflow status. See the expanded examples below for best practices.
25
- - **Keep Activities Idempotent:** Activities should be safe to retry and handle errors gracefully.
26
- - **Separate Concerns:** Keep workflows, activities, and schedules in separate files for clarity and maintainability.
27
- - **Check the Example Repo:** For real-world patterns, see [nestjs-temporal-core-example](https://github.com/harsh-simform/nestjs-temporal-core-example).
42
+ - **Declarative Decorators**: `@Activity()` and `@ActivityMethod()` for clean activity definitions
43
+ - 🔎 **Automatic Discovery**: Runtime discovery and registration of activities
44
+ - 📅 **Schedule Management**: Programmatic schedule creation and management
45
+ - 🏥 **Health Monitoring**: Built-in health checks and status reporting
46
+ - 🔌 **Connection Management**: Automatic connection pooling and lifecycle management
47
+ - 🛠️ **Error Handling**: Comprehensive error handling with structured logging
48
+ - 📊 **Performance Monitoring**: Built-in metrics and performance tracking
49
+ - 🔚 **Graceful Shutdown**: Clean resource cleanup and connection termination
50
+ - 📦 **Modular Design**: Use only what you need (client-only, worker-only, etc.)
28
51
 
29
- ---
52
+ ## Installation
30
53
 
31
- ## 🚀 Key Features
54
+ ```bash
55
+ npm install nestjs-temporal-core @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/common
56
+ ```
32
57
 
33
- - **🎯 NestJS-Native** - Familiar patterns: `@Activity`, `@Cron`, `@Interval`, `@Scheduled`
34
- - **🔍 Auto-Discovery** - Automatically finds and registers activities and schedules
35
- - **📅 Declarative Scheduling** - Built-in cron and interval scheduling with validation
36
- - **🔄 Unified Service** - Single `TemporalService` for all operations
37
- - **⚙️ Flexible Setup** - Client-only, worker-only, or unified deployments
38
- - **🏥 Health Monitoring** - Comprehensive status monitoring and health checks
39
- - **🔧 Production Ready** - TLS, connection management, graceful shutdowns
40
- - **📊 Modular Architecture** - Individual modules for specific needs
41
- - **📝 Configurable Logging** - Fine-grained control with `TemporalLogger`
42
- - **🔐 Enterprise Ready** - Temporal Cloud support with TLS and API keys
43
- - **🛠️ Developer Experience** - Rich TypeScript support with comprehensive utilities
44
- - **⚡ Performance Optimized** - Efficient metadata handling and caching
58
+ ### Peer Dependencies
45
59
 
46
- ## 📦 Installation
60
+ The package requires the following peer dependencies:
47
61
 
48
62
  ```bash
49
- npm install nestjs-temporal-core @temporalio/client @temporalio/worker @temporalio/workflow
63
+ npm install @nestjs/common @nestjs/core reflect-metadata rxjs
50
64
  ```
51
65
 
52
- ## 🏗️ Architecture
66
+ ## Quick Start
53
67
 
54
- NestJS Temporal Core is built with a modular architecture:
68
+ ### 1. Enable Shutdown Hooks
55
69
 
56
- ```text
57
- nestjs-temporal-core/
58
- ├── src/
59
- │ ├── decorators/ # Activity, workflow, and scheduling decorators
60
- │ ├── client/ # Temporal client management
61
- │ ├── worker/ # Worker lifecycle and management
62
- │ ├── activity/ # Activity discovery and execution
63
- │ ├── schedules/ # Schedule management
64
- │ ├── discovery/ # Auto-discovery services
65
- │ ├── utils/ # Utilities (validation, metadata, logging)
66
- │ ├── constants.ts # Predefined constants and expressions
67
- │ ├── interfaces.ts # TypeScript interfaces and types
68
- │ ├── temporal.module.ts # Main module
69
- │ └── temporal.service.ts # Unified service
70
- ```
70
+ First, enable shutdown hooks in your `main.ts` for proper Temporal resource cleanup:
71
+
72
+ ```typescript
73
+ // main.ts
74
+ import { NestFactory } from '@nestjs/core';
75
+ import { AppModule } from './app.module';
71
76
 
72
- ## 🚀 Quick Start
77
+ async function bootstrap() {
78
+ const app = await NestFactory.create(AppModule);
79
+
80
+ // Required for graceful Temporal connection cleanup
81
+ app.enableShutdownHooks();
82
+
83
+ await app.listen(3000);
84
+ }
85
+ bootstrap();
86
+ ```
73
87
 
74
- ### 1. Complete Integration (Recommended)
88
+ ### 2. Configure the Module
75
89
 
76
- For applications that need full Temporal functionality:
90
+ Import and configure `TemporalModule` in your app module:
77
91
 
78
92
  ```typescript
79
93
  // app.module.ts
80
94
  import { Module } from '@nestjs/common';
81
95
  import { TemporalModule } from 'nestjs-temporal-core';
82
- import { EmailActivities } from './activities/email.activities';
96
+ import { PaymentActivity } from './activities/payment.activity';
97
+ import { EmailActivity } from './activities/email.activity';
83
98
 
84
99
  @Module({
85
100
  imports: [
@@ -88,888 +103,1935 @@ import { EmailActivities } from './activities/email.activities';
88
103
  address: 'localhost:7233',
89
104
  namespace: 'default',
90
105
  },
91
- taskQueue: 'main-queue',
106
+ taskQueue: 'my-task-queue',
92
107
  worker: {
93
- workflowsPath: './dist/workflows',
94
- activityClasses: [EmailActivities],
95
- autoStart: true
96
- }
97
- })
108
+ workflowsPath: require.resolve('./workflows'),
109
+ activityClasses: [PaymentActivity, EmailActivity],
110
+ autoStart: true,
111
+ },
112
+ }),
98
113
  ],
99
- providers: [EmailActivities], // Auto-discovered
114
+ providers: [PaymentActivity, EmailActivity],
100
115
  })
101
116
  export class AppModule {}
102
117
  ```
103
118
 
104
- ### 2. Define Activities
119
+ ### 3. Define Activities
120
+
121
+ Create activities using `@Activity()` and `@ActivityMethod()` decorators:
105
122
 
106
123
  ```typescript
107
- // activities/email.activities.ts
124
+ // payment.activity.ts
108
125
  import { Injectable } from '@nestjs/common';
109
126
  import { Activity, ActivityMethod } from 'nestjs-temporal-core';
110
127
 
111
- @Activity()
128
+ export interface PaymentData {
129
+ amount: number;
130
+ currency: string;
131
+ customerId: string;
132
+ }
133
+
112
134
  @Injectable()
113
- export class EmailActivities {
135
+ @Activity({ name: 'payment-activities' })
136
+ export class PaymentActivity {
114
137
 
115
- @ActivityMethod({
116
- name: 'sendEmail',
117
- timeout: '30s',
118
- maxRetries: 3
119
- })
120
- async sendEmail(to: string, subject: string, body: string): Promise<void> {
121
- console.log(`Sending email to ${to}: ${subject}`);
122
- // Your email sending logic here
138
+ @ActivityMethod('processPayment')
139
+ async processPayment(data: PaymentData): Promise<{ transactionId: string }> {
140
+ // Payment processing logic with full NestJS DI support
141
+ console.log(`Processing payment: $${data.amount} ${data.currency}`);
142
+
143
+ // Simulate payment processing
144
+ await new Promise(resolve => setTimeout(resolve, 1000));
145
+
146
+ return { transactionId: `txn_${Date.now()}` };
123
147
  }
124
148
 
125
- @ActivityMethod('sendNotification')
126
- async sendNotification(userId: string, message: string): Promise<void> {
127
- console.log(`Notifying user ${userId}: ${message}`);
128
- // Your notification logic here
149
+ @ActivityMethod('refundPayment')
150
+ async refundPayment(transactionId: string): Promise<{ refundId: string }> {
151
+ // Refund logic
152
+ console.log(`Refunding transaction: ${transactionId}`);
153
+ return { refundId: `ref_${Date.now()}` };
129
154
  }
130
155
  }
131
156
  ```
132
157
 
133
- ### 3. Create Workflows
134
-
135
- ```typescript
136
- // workflows/email.workflow.ts
137
- import { proxyActivities } from '@temporalio/workflow';
138
- import type { EmailActivities } from '../activities/email.activities';
139
-
140
- const { sendEmail, sendNotification } = proxyActivities<EmailActivities>({
141
- startToCloseTimeout: '1 minute',
142
- });
143
-
144
- export async function processEmailWorkflow(
145
- userId: string,
146
- emailData: { to: string; subject: string; body: string }
147
- ): Promise<void> {
148
- // Send email
149
- await sendEmail(emailData.to, emailData.subject, emailData.body);
150
-
151
- // Send notification
152
- await sendNotification(userId, 'Email sent successfully');
153
- }
154
- ```
155
-
156
- ### 4. Schedule Workflows
158
+ ### 4. Define Workflows
157
159
 
158
- > **Note:** Schedules in NestJS Temporal Core trigger workflows, not service methods. The decorated method is used to define the schedule metadata, but the actual execution runs the workflow you specify.
160
+ Create workflows as pure Temporal functions (NOT NestJS services):
159
161
 
160
162
  ```typescript
161
- // services/scheduled.service.ts
162
- import { Injectable } from '@nestjs/common';
163
- import {
164
- Scheduled,
165
- CRON_EXPRESSIONS,
166
- } from 'nestjs-temporal-core';
167
-
168
- @Injectable()
169
- export class ScheduledService {
170
- @Scheduled({
171
- scheduleId: 'daily-report',
172
- cron: CRON_EXPRESSIONS.DAILY_8AM,
173
- description: 'Generate daily sales report',
174
- taskQueue: 'reports',
175
- workflowType: 'generateReportWorkflow', // Name of the workflow function to run
176
- workflowArgs: [{ reportType: 'sales' }], // Arguments passed to the workflow
177
- })
178
- async generateDailyReport(): Promise<void> {
179
- // This method is NOT executed directly. Instead, the schedule triggers the workflow specified above.
180
- }
181
- }
182
- ```
183
-
184
- - The `@Scheduled` decorator registers a schedule with Temporal.
185
- - The `workflowType` property specifies the workflow function to run (must be exported and available to the worker).
186
- - The `workflowArgs` property allows you to pass arguments to the workflow when the schedule triggers.
187
-
188
- > **Best Practice:** Keep your scheduled workflow logic in a dedicated workflow file, and use the schedule only to trigger it with the desired arguments.
163
+ // payment.workflow.ts
164
+ import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
165
+ import type { PaymentActivity } from './payment.activity';
166
+
167
+ // Create activity proxies
168
+ const { processPayment, refundPayment } = proxyActivities<typeof PaymentActivity.prototype>({
169
+ startToCloseTimeout: '5m',
170
+ retry: {
171
+ maximumAttempts: 3,
172
+ initialInterval: '1s',
173
+ },
174
+ });
189
175
 
190
- ### 5. Parameter Injection in Workflows
176
+ // Define signals and queries
177
+ export const cancelPaymentSignal = defineSignal<[string]>('cancelPayment');
178
+ export const getPaymentStatusQuery = defineQuery<string>('getPaymentStatus');
191
179
 
192
- You can use parameter decorators to inject workflow metadata and context. This is most useful for advanced scenarios, such as handling signals and queries in a class-based workflow.
180
+ export async function processPaymentWorkflow(data: PaymentData): Promise<any> {
181
+ let status = 'processing';
182
+ let transactionId: string | undefined;
193
183
 
194
- #### Comprehensive Example: Handling Signals and Workflow State
184
+ // Set up signal and query handlers
185
+ setHandler(cancelPaymentSignal, (reason: string) => {
186
+ status = 'cancelled';
187
+ });
195
188
 
196
- ```typescript
197
- // workflows/order.workflow.ts
198
- import { Injectable } from '@nestjs/common';
199
- import {
200
- WorkflowParam,
201
- WorkflowContext,
202
- WorkflowId,
203
- RunId,
204
- TaskQueue,
205
- Signal,
206
- Query
207
- } from 'nestjs-temporal-core';
189
+ setHandler(getPaymentStatusQuery, () => status);
208
190
 
209
- @Injectable()
210
- export class OrderWorkflowController {
211
- private updateData: any = null; // Store signal data in workflow state
212
- private status: string = 'processing';
213
-
214
- async processOrder(
215
- @WorkflowParam(0) orderId: string,
216
- @WorkflowParam(1) customerData: any,
217
- @WorkflowId() workflowId: string,
218
- @WorkflowContext() context: any
219
- ): Promise<void> {
220
- // Main workflow logic
221
- // Wait for an update signal (example: polling or event-driven)
222
- while (!this.updateData) {
223
- // ...wait or yield...
224
- // In real Temporal workflows, use condition() or similar for waiting
191
+ try {
192
+ // Execute payment activity
193
+ const result = await processPayment(data);
194
+ transactionId = result.transactionId;
195
+ status = 'completed';
196
+
197
+ return {
198
+ success: true,
199
+ transactionId,
200
+ status,
201
+ };
202
+ } catch (error) {
203
+ status = 'failed';
204
+
205
+ // Compensate if needed
206
+ if (transactionId) {
207
+ await refundPayment(transactionId);
225
208
  }
226
- // Use updateData in your logic
227
- // ...
228
- this.status = 'completed';
229
- }
230
-
231
- @Signal('updateOrder')
232
- async updateOrder(@WorkflowParam() updateData: any): Promise<void> {
233
- // Handle order update signal
234
- this.updateData = updateData; // Store for use in processOrder
235
- this.status = 'updated';
236
- }
237
-
238
- @Query('getOrderStatus')
239
- getOrderStatus(@RunId() runId: string): string {
240
- // Return current order status
241
- return this.status;
209
+
210
+ throw error;
242
211
  }
243
212
  }
244
213
  ```
245
214
 
246
- - **Signal Handling:** Use a class property to persist signal data (`updateData`) so it can be accessed by the main workflow logic.
247
- - **Best Practice:** Always store signal data in workflow state (class property or closure variable) to ensure it is available after workflow replay.
248
- - **Forwarding Data:** The main workflow function (`processOrder`) can access and use the updated data as needed.
249
- - **Status Tracking:** Use a property like `status` to track and query workflow progress.
215
+ ### 5. Use in Services
250
216
 
251
- > **Tip:** In Temporal workflows, use `condition()` or similar mechanisms to wait for signals in a non-blocking, replay-safe way.
252
-
253
- ### 6. Use in Services
217
+ Inject `TemporalService` to start and manage workflows:
254
218
 
255
219
  ```typescript
256
- // services/order.service.ts
220
+ // payment.service.ts
257
221
  import { Injectable } from '@nestjs/common';
258
222
  import { TemporalService } from 'nestjs-temporal-core';
259
223
 
260
224
  @Injectable()
261
- export class OrderService {
225
+ export class PaymentService {
262
226
  constructor(private readonly temporal: TemporalService) {}
263
227
 
264
- async createOrder(orderData: any) {
265
- const { workflowId } = await this.temporal.startWorkflow(
266
- 'processOrder',
267
- [orderData],
268
- {
269
- taskQueue: 'orders',
270
- workflowId: `order-${orderData.id}`,
271
- searchAttributes: {
272
- 'customer-id': orderData.customerId
273
- }
228
+ async processPayment(paymentData: any) {
229
+ // Start workflow
230
+ const result = await this.temporal.startWorkflow(
231
+ 'processPaymentWorkflow',
232
+ [paymentData],
233
+ {
234
+ workflowId: `payment-${Date.now()}`,
235
+ taskQueue: 'my-task-queue',
274
236
  }
275
237
  );
276
238
 
277
- return { workflowId };
239
+ return {
240
+ workflowId: result.result.workflowId,
241
+ runId: result.result.runId,
242
+ };
278
243
  }
279
244
 
280
- async cancelOrder(orderId: string) {
281
- await this.temporal.signalWorkflow(`order-${orderId}`, 'cancel');
282
- return { cancelled: true };
245
+ async checkPaymentStatus(workflowId: string) {
246
+ // Query workflow
247
+ const statusResult = await this.temporal.queryWorkflow(
248
+ workflowId,
249
+ 'getPaymentStatus'
250
+ );
251
+
252
+ return { status: statusResult.result };
283
253
  }
284
254
 
285
- async getOrderStatus(orderId: string) {
286
- const status = await this.temporal.queryWorkflow(`order-${orderId}`, 'getStatus');
287
- return status;
255
+ async cancelPayment(workflowId: string, reason: string) {
256
+ // Send signal
257
+ await this.temporal.signalWorkflow(
258
+ workflowId,
259
+ 'cancelPayment',
260
+ [reason]
261
+ );
288
262
  }
289
263
  }
290
264
  ```
291
265
 
292
- ## 🛠️ Utilities and Constants
293
-
294
- NestJS Temporal Core provides comprehensive utilities and predefined constants for common use cases:
295
-
296
- ### Predefined Constants
297
-
298
- ```typescript
299
- import {
300
- CRON_EXPRESSIONS,
301
- INTERVAL_EXPRESSIONS,
302
- TIMEOUTS,
303
- RETRY_POLICIES
304
- } from 'nestjs-temporal-core';
305
-
306
- // Cron expressions
307
- console.log(CRON_EXPRESSIONS.DAILY_8AM); // '0 8 * * *'
308
- console.log(CRON_EXPRESSIONS.WEEKLY_MONDAY_9AM); // '0 9 * * 1'
309
- console.log(CRON_EXPRESSIONS.MONTHLY_FIRST); // '0 0 1 * *'
310
-
311
- // Interval expressions
312
- console.log(INTERVAL_EXPRESSIONS.EVERY_5_MINUTES); // '5m'
313
- console.log(INTERVAL_EXPRESSIONS.EVERY_HOUR); // '1h'
314
- console.log(INTERVAL_EXPRESSIONS.DAILY); // '24h'
315
-
316
- // Timeout values
317
- console.log(TIMEOUTS.ACTIVITY_SHORT); // '1m'
318
- console.log(TIMEOUTS.WORKFLOW_MEDIUM); // '24h'
319
- console.log(TIMEOUTS.CONNECTION_TIMEOUT); // '10s'
320
-
321
- // Retry policies
322
- console.log(RETRY_POLICIES.QUICK.maximumAttempts); // 3
323
- console.log(RETRY_POLICIES.STANDARD.backoffCoefficient); // 2.0
324
- ```
325
-
326
- ### Validation Utilities
327
-
328
- ```typescript
329
- import {
330
- isValidCronExpression,
331
- isValidIntervalExpression
332
- } from 'nestjs-temporal-core';
333
-
334
- // Validate cron expressions
335
- console.log(isValidCronExpression('0 8 * * *')); // true
336
- console.log(isValidCronExpression('invalid')); // false
337
-
338
- // Validate interval expressions
339
- console.log(isValidIntervalExpression('5m')); // true
340
- console.log(isValidIntervalExpression('2h')); // true
341
- console.log(isValidIntervalExpression('bad')); // false
342
- ```
343
-
344
- ### Metadata Utilities
345
-
346
- ```typescript
347
- import {
348
- isActivity,
349
- getActivityMetadata,
350
- isActivityMethod,
351
- getActivityMethodMetadata,
352
- getParameterMetadata
353
- } from 'nestjs-temporal-core';
354
-
355
- // Check if a class is marked as an Activity
356
- @Activity({ taskQueue: 'my-queue' })
357
- class MyActivity {}
266
+ ## Module Variants
358
267
 
359
- console.log(isActivity(MyActivity)); // true
360
- const metadata = getActivityMetadata(MyActivity);
361
- console.log(metadata.taskQueue); // 'my-queue'
268
+ The package provides modular architecture with separate modules for different use cases:
362
269
 
363
- // Check method metadata
364
- const methodMetadata = getActivityMethodMetadata(MyActivity.prototype.myMethod);
365
- ```
270
+ ### 1. Unified Module (Recommended)
366
271
 
367
- ### Logging Configuration
272
+ Complete integration with both client and worker capabilities:
368
273
 
369
274
  ```typescript
370
- import { TemporalLogger, TemporalLoggerManager } from 'nestjs-temporal-core';
371
-
372
- // Configure logging
373
- const logger = TemporalLoggerManager.getInstance();
374
- logger.configure({
375
- enableLogger: true,
376
- logLevel: 'info',
377
- appName: 'My Temporal App'
378
- });
379
-
380
- // Use in your services
381
- @Injectable()
382
- export class MyService {
383
- private readonly logger = new TemporalLogger(MyService.name);
275
+ import { TemporalModule } from 'nestjs-temporal-core';
384
276
 
385
- async doSomething() {
386
- this.logger.info('Starting operation');
387
- this.logger.error('Something went wrong', { context: 'additional data' });
388
- }
389
- }
277
+ TemporalModule.register({
278
+ connection: { address: 'localhost:7233' },
279
+ taskQueue: 'my-queue',
280
+ worker: {
281
+ workflowsPath: require.resolve('./workflows'),
282
+ activityClasses: [PaymentActivity, EmailActivity],
283
+ },
284
+ })
390
285
  ```
391
286
 
392
- ## 🏗️ Integration Patterns
287
+ ### 2. Client-Only Module
393
288
 
394
- ### Client-Only Integration
395
-
396
- For applications that only start workflows (e.g., web APIs):
289
+ For services that only need to start/query workflows:
397
290
 
398
291
  ```typescript
399
- import { TemporalClientModule } from 'nestjs-temporal-core';
292
+ import { TemporalClientModule } from 'nestjs-temporal-core/client';
400
293
 
401
- @Module({
402
- imports: [
403
- TemporalClientModule.forRoot({
404
- connection: {
405
- address: 'localhost:7233',
406
- namespace: 'production'
407
- }
408
- })
409
- ],
410
- providers: [ApiService],
294
+ TemporalClientModule.register({
295
+ connection: { address: 'localhost:7233' },
296
+ namespace: 'default',
411
297
  })
412
- export class ClientOnlyModule {}
413
298
  ```
414
299
 
415
- ### Worker-Only Integration
300
+ ### 3. Worker-Only Module
416
301
 
417
302
  For dedicated worker processes:
418
303
 
419
304
  ```typescript
420
- import { TemporalWorkerModule, WORKER_PRESETS } from 'nestjs-temporal-core';
305
+ import { TemporalWorkerModule } from 'nestjs-temporal-core/worker';
421
306
 
422
- @Module({
423
- imports: [
424
- TemporalWorkerModule.forRoot({
425
- connection: {
426
- address: 'localhost:7233',
427
- namespace: 'production'
428
- },
429
- taskQueue: 'worker-queue',
430
- workflowsPath: './dist/workflows',
431
- activityClasses: [ProcessingActivities],
432
- workerOptions: WORKER_PRESETS.PRODUCTION_HIGH_THROUGHPUT
433
- })
434
- ],
435
- providers: [ProcessingActivities],
307
+ TemporalWorkerModule.register({
308
+ connection: { address: 'localhost:7233' },
309
+ taskQueue: 'worker-queue',
310
+ worker: {
311
+ workflowsPath: require.resolve('./workflows'),
312
+ activityClasses: [BackgroundActivity],
313
+ },
436
314
  })
437
- export class WorkerOnlyModule {}
438
315
  ```
439
316
 
440
- ### Modular Integration
317
+ ### 4. Activity-Only Module
441
318
 
442
- Using individual modules for specific needs:
319
+ For standalone activity management:
443
320
 
444
321
  ```typescript
445
- import {
446
- TemporalClientModule,
447
- TemporalActivityModule,
448
- TemporalSchedulesModule
449
- } from 'nestjs-temporal-core';
322
+ import { TemporalActivityModule } from 'nestjs-temporal-core/activity';
450
323
 
451
- @Module({
452
- imports: [
453
- // Client for workflow operations
454
- TemporalClientModule.forRoot({
455
- connection: { address: 'localhost:7233' }
456
- }),
457
-
458
- // Activities management
459
- TemporalActivityModule.forRoot({
460
- activityClasses: [EmailActivities, PaymentActivities]
461
- }),
462
-
463
- // Schedule management
464
- TemporalSchedulesModule.forRoot({
465
- autoStart: true,
466
- defaultTimezone: 'UTC'
467
- }),
468
- ],
469
- providers: [EmailActivities, PaymentActivities, ScheduledService],
324
+ TemporalActivityModule.register({
325
+ activityClasses: [DataProcessingActivity],
470
326
  })
471
- export class ModularIntegrationModule {}
472
327
  ```
473
328
 
474
- ## ⚙️ Configuration
329
+ ### 5. Schedules-Only Module
475
330
 
476
- ### Async Configuration
331
+ For managing Temporal schedules:
477
332
 
478
333
  ```typescript
479
- import { ConfigModule, ConfigService } from '@nestjs/config';
334
+ import { TemporalSchedulesModule } from 'nestjs-temporal-core/schedules';
480
335
 
481
- @Module({
482
- imports: [
483
- ConfigModule.forRoot(),
484
- TemporalModule.registerAsync({
485
- imports: [ConfigModule],
486
- useFactory: async (config: ConfigService) => ({
487
- connection: {
488
- address: config.get('TEMPORAL_ADDRESS'),
489
- namespace: config.get('TEMPORAL_NAMESPACE'),
490
- tls: config.get('TEMPORAL_TLS_ENABLED') === 'true',
491
- apiKey: config.get('TEMPORAL_API_KEY'),
492
- },
493
- taskQueue: config.get('TEMPORAL_TASK_QUEUE'),
494
- worker: {
495
- workflowsPath: config.get('WORKFLOWS_PATH'),
496
- activityClasses: [EmailActivities, PaymentActivities],
497
- autoStart: config.get('WORKER_AUTO_START') !== 'false',
498
- }
499
- }),
500
- inject: [ConfigService],
501
- })
502
- ],
336
+ TemporalSchedulesModule.register({
337
+ connection: { address: 'localhost:7233' },
503
338
  })
504
- export class AppModule {}
505
339
  ```
506
340
 
507
- ### Environment-Specific Configurations
341
+ ## Configuration
342
+
343
+ ## Configuration
344
+
345
+ ### Basic Configuration
508
346
 
509
347
  ```typescript
510
- // Development
511
- const developmentConfig = {
348
+ TemporalModule.register({
512
349
  connection: {
513
350
  address: 'localhost:7233',
514
- namespace: 'development'
351
+ namespace: 'default',
515
352
  },
516
- taskQueue: 'dev-queue',
353
+ taskQueue: 'my-task-queue',
517
354
  worker: {
518
- workflowsPath: './dist/workflows',
519
- workerOptions: WORKER_PRESETS.DEVELOPMENT
520
- }
521
- };
522
-
523
- // Production
524
- const productionConfig = {
525
- connection: {
526
- address: process.env.TEMPORAL_ADDRESS!,
527
- namespace: process.env.TEMPORAL_NAMESPACE!,
528
- tls: true,
529
- apiKey: process.env.TEMPORAL_API_KEY
355
+ workflowsPath: require.resolve('./workflows'),
356
+ activityClasses: [PaymentActivity, EmailActivity],
357
+ autoStart: true,
358
+ maxConcurrentActivityExecutions: 100,
530
359
  },
531
- taskQueue: process.env.TEMPORAL_TASK_QUEUE!,
532
- worker: {
533
- workflowBundle: require('../workflows/bundle'), // Pre-bundled
534
- workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
535
- }
536
- };
360
+ logLevel: 'info',
361
+ enableLogger: true,
362
+ })
537
363
  ```
538
364
 
539
- ## 📝 Logger Configuration
540
-
541
- Control logging behavior across all Temporal modules with configurable logger settings:
365
+ ### Multiple Workers Configuration
542
366
 
543
- ### Basic Logger Setup
367
+ **New in 3.0.12**: Support for multiple workers with different task queues in the same process.
544
368
 
545
369
  ```typescript
546
- // Enable/disable logging and set log levels
547
370
  TemporalModule.register({
548
371
  connection: {
549
372
  address: 'localhost:7233',
550
- namespace: 'default'
373
+ namespace: 'default',
551
374
  },
552
- taskQueue: 'main-queue',
553
- // Logger configuration
554
- enableLogger: true, // Enable/disable all logging
555
- logLevel: 'info', // Set log level: 'error' | 'warn' | 'info' | 'debug' | 'verbose'
556
- worker: {
557
- workflowsPath: './dist/workflows',
558
- activityClasses: [EmailActivities]
559
- }
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,
560
403
  })
561
404
  ```
562
405
 
563
- ### Environment-Based Logger Configuration
406
+ #### Accessing Multiple Workers
564
407
 
565
408
  ```typescript
566
- // Different log levels for different environments
567
- const loggerConfig = {
568
- development: {
569
- enableLogger: true,
570
- logLevel: 'debug' as const // Show all logs in development
571
- },
572
- production: {
573
- enableLogger: true,
574
- logLevel: 'warn' as const // Only warnings and errors in production
575
- },
576
- testing: {
577
- enableLogger: false // Disable logging during tests
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
+ }
578
427
  }
579
- };
580
428
 
581
- TemporalModule.register({
582
- connection: { address: 'localhost:7233' },
583
- taskQueue: 'main-queue',
584
- ...loggerConfig[process.env.NODE_ENV || 'development'],
585
- worker: {
586
- workflowsPath: './dist/workflows'
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');
587
435
  }
588
- })
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
+ }
589
451
  ```
590
452
 
591
- ### Individual Module Logger Configuration
453
+ ### Manual Worker Creation (Advanced)
592
454
 
593
- Configure logging for specific modules:
455
+ For users who need full control, you can access the native Temporal connection to create custom workers:
594
456
 
595
457
  ```typescript
596
- // Activity Module with custom logging
597
- TemporalActivityModule.forRoot({
598
- activityClasses: [EmailActivities],
599
- enableLogger: true,
600
- logLevel: 'debug'
601
- })
458
+ import { Injectable, OnModuleInit } from '@nestjs/common';
459
+ import { TemporalService } from 'nestjs-temporal-core';
460
+ import { Worker } from '@temporalio/worker';
602
461
 
603
- // Schedules Module with minimal logging
604
- TemporalSchedulesModule.forRoot({
605
- autoStart: true,
606
- enableLogger: true,
607
- logLevel: 'error' // Only show errors
608
- })
462
+ @Injectable()
463
+ export class CustomWorkerService implements OnModuleInit {
464
+ private customWorker: Worker;
609
465
 
610
- // Client Module with no logging
611
- TemporalClientModule.forRoot({
612
- connection: { address: 'localhost:7233' },
613
- enableLogger: false
614
- })
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
+ }
615
493
  ```
616
494
 
617
- ### Log Level Hierarchy
495
+ ### Async Configuration
496
+
497
+ For dynamic configuration using environment variables or config services:
498
+
499
+ ```typescript
500
+ // config/temporal.config.ts
501
+ import { Injectable } from '@nestjs/common';
502
+ import { ConfigService } from '@nestjs/config';
503
+ import { TemporalOptionsFactory, TemporalOptions } from 'nestjs-temporal-core';
504
+
505
+ @Injectable()
506
+ export class TemporalConfigService implements TemporalOptionsFactory {
507
+ constructor(private configService: ConfigService) {}
508
+
509
+ createTemporalOptions(): TemporalOptions {
510
+ return {
511
+ connection: {
512
+ address: this.configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
513
+ namespace: this.configService.get('TEMPORAL_NAMESPACE', 'default'),
514
+ },
515
+ taskQueue: this.configService.get('TEMPORAL_TASK_QUEUE', 'default'),
516
+ worker: {
517
+ workflowsPath: require.resolve('../workflows'),
518
+ activityClasses: [], // Populated by module
519
+ maxConcurrentActivityExecutions: 100,
520
+ },
521
+ };
522
+ }
523
+ }
618
524
 
619
- The logger follows a hierarchical structure where each level includes all levels above it:
525
+ // app.module.ts
526
+ import { ConfigModule } from '@nestjs/config';
620
527
 
621
- - **`error`**: Only critical errors
622
- - **`warn`**: Errors + warnings
623
- - **`info`**: Errors + warnings + informational messages (default)
624
- - **`debug`**: Errors + warnings + info + debug information
625
- - **`verbose`**: All messages including verbose details
528
+ @Module({
529
+ imports: [
530
+ ConfigModule.forRoot({ isGlobal: true }),
531
+ TemporalModule.registerAsync({
532
+ imports: [ConfigModule],
533
+ useClass: TemporalConfigService,
534
+ }),
535
+ ],
536
+ })
537
+ export class AppModule {}
538
+ ```
626
539
 
627
- ### Async Configuration with Logger
540
+ ### Alternative Async Pattern (useFactory)
628
541
 
629
542
  ```typescript
630
543
  TemporalModule.registerAsync({
631
544
  imports: [ConfigModule],
632
- useFactory: (config: ConfigService) => ({
545
+ useFactory: (configService: ConfigService) => ({
633
546
  connection: {
634
- address: config.get('TEMPORAL_ADDRESS'),
635
- namespace: config.get('TEMPORAL_NAMESPACE')
547
+ address: configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
548
+ namespace: configService.get('TEMPORAL_NAMESPACE', 'default'),
636
549
  },
637
- taskQueue: config.get('TEMPORAL_TASK_QUEUE'),
638
- // Dynamic logger configuration
639
- enableLogger: config.get('TEMPORAL_LOGGING_ENABLED', 'true') === 'true',
640
- logLevel: config.get('TEMPORAL_LOG_LEVEL', 'info'),
550
+ taskQueue: configService.get('TEMPORAL_TASK_QUEUE', 'default'),
641
551
  worker: {
642
- workflowsPath: './dist/workflows'
643
- }
552
+ workflowsPath: require.resolve('./workflows'),
553
+ activityClasses: [PaymentActivity, EmailActivity],
554
+ },
644
555
  }),
645
- inject: [ConfigService]
556
+ inject: [ConfigService],
646
557
  })
647
558
  ```
648
559
 
649
- ### Logger Examples
560
+ ### TLS Configuration (Temporal Cloud)
561
+
562
+ For secure connections to Temporal Cloud:
650
563
 
651
564
  ```typescript
652
- // Silent mode - no logs
653
- {
654
- enableLogger: false
655
- }
565
+ import * as fs from 'fs';
656
566
 
657
- // Error only - for production monitoring
658
- {
659
- enableLogger: true,
660
- logLevel: 'error'
661
- }
567
+ TemporalModule.register({
568
+ connection: {
569
+ address: 'your-namespace.your-account.tmprl.cloud:7233',
570
+ namespace: 'your-namespace.your-account',
571
+ tls: {
572
+ clientCertPair: {
573
+ crt: fs.readFileSync('/path/to/client.crt'),
574
+ key: fs.readFileSync('/path/to/client.key'),
575
+ },
576
+ },
577
+ },
578
+ taskQueue: 'my-task-queue',
579
+ worker: {
580
+ workflowsPath: require.resolve('./workflows'),
581
+ activityClasses: [PaymentActivity],
582
+ },
583
+ })
584
+ ```
662
585
 
663
- // Development mode - detailed logging
664
- {
665
- enableLogger: true,
666
- logLevel: 'debug'
667
- }
586
+ ### Configuration Options Reference
668
587
 
669
- // Verbose mode - maximum detail for troubleshooting
670
- {
671
- enableLogger: true,
672
- logLevel: 'verbose'
588
+ ```typescript
589
+ interface TemporalOptions {
590
+ // Connection settings
591
+ connection: {
592
+ address: string; // Temporal server address (default: 'localhost:7233')
593
+ namespace?: string; // Temporal namespace (default: 'default')
594
+ tls?: TLSConfig; // TLS configuration for secure connections
595
+ };
596
+
597
+ // Task queue name
598
+ taskQueue?: string; // Default task queue (default: 'default')
599
+
600
+ // Worker configuration
601
+ worker?: {
602
+ workflowsPath?: string; // Path to workflow definitions (use require.resolve)
603
+ activityClasses?: any[]; // Array of activity classes to register
604
+ autoStart?: boolean; // Auto-start worker on module init (default: true)
605
+ maxConcurrentActivityExecutions?: number; // Max concurrent activities (default: 100)
606
+ maxActivitiesPerSecond?: number; // Rate limit for activities
607
+ };
608
+
609
+ // Logging
610
+ logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error'; // Log level (default: 'info')
611
+ enableLogger?: boolean; // Enable logging (default: true)
612
+
613
+ // Advanced
614
+ isGlobal?: boolean; // Make module global (default: false)
615
+ autoRestart?: boolean; // Auto-restart worker on failure (default: true)
673
616
  }
674
617
  ```
675
618
 
676
- ## 📊 Health Monitoring
619
+ ## Core Concepts
677
620
 
678
- Built-in health monitoring for production environments:
621
+ ### Activities
679
622
 
680
- ```typescript
681
- @Controller('health')
682
- export class HealthController {
683
- constructor(private readonly temporal: TemporalService) {}
623
+ Activities are NestJS services decorated with `@Activity()` that perform actual work. They have full access to NestJS dependency injection and can interact with external systems.
684
624
 
685
- @Get('temporal')
686
- async getTemporalHealth() {
687
- const health = await this.temporal.getOverallHealth();
688
- return {
689
- status: health.status,
690
- components: health.components,
691
- timestamp: new Date().toISOString()
692
- };
625
+ **Key Points:**
626
+ - Activities are NestJS services (`@Injectable()`)
627
+ - Use `@Activity()` decorator at class level
628
+ - Use `@ActivityMethod()` decorator for methods to be registered
629
+ - Activities should be idempotent and handle retries gracefully
630
+ - Full access to NestJS DI (inject services, repositories, etc.)
631
+
632
+ ```typescript
633
+ @Injectable()
634
+ @Activity({ name: 'order-activities' })
635
+ export class OrderActivity {
636
+ constructor(
637
+ private readonly orderRepository: OrderRepository,
638
+ private readonly emailService: EmailService,
639
+ ) {}
640
+
641
+ @ActivityMethod('createOrder')
642
+ async createOrder(orderData: CreateOrderData): Promise<Order> {
643
+ // Database operations with full DI support
644
+ const order = await this.orderRepository.create(orderData);
645
+ await this.emailService.sendConfirmation(order);
646
+ return order;
693
647
  }
694
648
 
695
- @Get('temporal/detailed')
696
- async getDetailedStatus() {
697
- const systemStatus = await this.temporal.getSystemStatus();
698
- const stats = this.temporal.getDiscoveryStats();
699
-
649
+ @ActivityMethod('validateInventory')
650
+ async validateInventory(items: OrderItem[]): Promise<boolean> {
651
+ // Business logic with injected services
652
+ return await this.orderRepository.checkInventory(items);
653
+ }
654
+ }
655
+ ```
656
+
657
+ ### Workflows
658
+
659
+ Workflows are **pure Temporal functions** (NOT NestJS services) that orchestrate activities. They must be deterministic and use Temporal's workflow APIs.
660
+
661
+ **Important:** Workflows are NOT decorated with `@Injectable()` and should NOT use NestJS dependency injection.
662
+
663
+ ```typescript
664
+ // order.workflow.ts
665
+ import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
666
+ import type { OrderActivity } from './order.activity';
667
+
668
+ // Create activity proxies with proper typing
669
+ const { createOrder, validateInventory } = proxyActivities<typeof OrderActivity.prototype>({
670
+ startToCloseTimeout: '5m',
671
+ retry: {
672
+ maximumAttempts: 3,
673
+ initialInterval: '1s',
674
+ maximumInterval: '30s',
675
+ },
676
+ });
677
+
678
+ // Define signals and queries at module level
679
+ export const cancelOrderSignal = defineSignal<[string]>('cancelOrder');
680
+ export const getOrderStatusQuery = defineQuery<string>('getOrderStatus');
681
+
682
+ // Workflow function (exported, not a class)
683
+ export async function processOrderWorkflow(orderData: CreateOrderData): Promise<OrderResult> {
684
+ let status = 'pending';
685
+
686
+ // Set up signal handler
687
+ setHandler(cancelOrderSignal, (reason: string) => {
688
+ status = 'cancelled';
689
+ });
690
+
691
+ // Set up query handler
692
+ setHandler(getOrderStatusQuery, () => status);
693
+
694
+ try {
695
+ // Validate inventory
696
+ const isValid = await validateInventory(orderData.items);
697
+ if (!isValid) {
698
+ throw new Error('Insufficient inventory');
699
+ }
700
+
701
+ // Create order
702
+ status = 'processing';
703
+ const order = await createOrder(orderData);
704
+ status = 'completed';
705
+
700
706
  return {
701
- system: systemStatus,
702
- discovery: stats,
703
- schedules: this.temporal.getScheduleStats()
707
+ orderId: order.id,
708
+ status,
704
709
  };
710
+ } catch (error) {
711
+ status = 'failed';
712
+ throw error;
705
713
  }
706
714
  }
707
715
  ```
708
716
 
709
- ## 🔧 Advanced Features
717
+ ### Signals and Queries
718
+
719
+ Signals allow external systems to send events to workflows, while queries provide read-only access to workflow state.
720
+
721
+ ```typescript
722
+ import { defineSignal, defineQuery, setHandler, condition } from '@temporalio/workflow';
723
+
724
+ // Define at module level
725
+ export const updateStatusSignal = defineSignal<[string]>('updateStatus');
726
+ export const addItemSignal = defineSignal<[Item]>('addItem');
727
+ export const getItemsQuery = defineQuery<Item[]>('getItems');
728
+ export const getStatusQuery = defineQuery<string>('getStatus');
729
+
730
+ export async function myWorkflow(): Promise<void> {
731
+ let status = 'pending';
732
+ const items: Item[] = [];
733
+
734
+ // Set up handlers
735
+ setHandler(updateStatusSignal, (newStatus: string) => {
736
+ status = newStatus;
737
+ });
738
+
739
+ setHandler(addItemSignal, (item: Item) => {
740
+ items.push(item);
741
+ });
742
+
743
+ setHandler(getItemsQuery, () => items);
744
+ setHandler(getStatusQuery, () => status);
745
+
746
+ // Wait for completion signal
747
+ await condition(() => status === 'completed');
748
+ }
749
+ ```
750
+
751
+ ### Using Workflows in Services
710
752
 
711
- ### Activity Options
753
+ Inject `TemporalService` in your NestJS services to interact with workflows:
712
754
 
713
755
  ```typescript
714
- @Activity()
715
756
  @Injectable()
716
- export class PaymentActivities {
757
+ export class OrderService {
758
+ constructor(private readonly temporal: TemporalService) {}
759
+
760
+ async createOrder(orderData: CreateOrderData) {
761
+ // Start workflow - note the method signature
762
+ const result = await this.temporal.startWorkflow(
763
+ 'processOrderWorkflow', // Workflow function name
764
+ [orderData], // Arguments array
765
+ { // Options
766
+ workflowId: `order-${Date.now()}`,
767
+ taskQueue: 'order-queue',
768
+ }
769
+ );
770
+
771
+ return {
772
+ workflowId: result.result.workflowId,
773
+ runId: result.result.runId,
774
+ };
775
+ }
776
+
777
+ async queryOrderStatus(workflowId: string) {
778
+ const result = await this.temporal.queryWorkflow(
779
+ workflowId,
780
+ 'getOrderStatus'
781
+ );
782
+
783
+ return result.result;
784
+ }
785
+
786
+ async cancelOrder(workflowId: string, reason: string) {
787
+ await this.temporal.signalWorkflow(
788
+ workflowId,
789
+ 'cancelOrder',
790
+ [reason]
791
+ );
792
+ }
793
+ }
794
+ ```
795
+
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:
893
+
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
+ ```
905
+
906
+ ### Result Types
907
+
908
+ ```typescript
909
+ interface WorkflowExecutionResult<T> {
910
+ success: boolean;
911
+ result: T; // Contains workflowId, runId, etc.
912
+ executionTime: number;
913
+ error?: Error;
914
+ }
915
+
916
+ interface WorkflowQueryResult<T> {
917
+ success: boolean;
918
+ result: T;
919
+ workflowId: string;
920
+ queryName: string;
921
+ }
922
+
923
+ interface WorkflowSignalResult {
924
+ success: boolean;
925
+ workflowId: string;
926
+ signalName: string;
927
+ }
928
+ ```
929
+
930
+ ## Examples
931
+
932
+ ## Examples
933
+
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
+ ) {}
717
948
 
718
- @ActivityMethod({
719
- name: 'processPayment',
720
- timeout: '2m',
721
- maxRetries: 5,
722
- retryPolicy: {
723
- maximumAttempts: 5,
724
- initialInterval: '1s',
725
- maximumInterval: '60s',
726
- backoffCoefficient: 2.0
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
+ }
1057
+
1058
+ if (transactionId) {
1059
+ await refundPayment(transactionId);
727
1060
  }
728
- })
729
- async processPayment(orderId: string, amount: number) {
730
- // Complex payment processing with retries
1061
+
1062
+ status = 'failed';
1063
+ throw error;
1064
+ }
1065
+ }
1066
+
1067
+ // order.service.ts
1068
+ @Injectable()
1069
+ export class OrderService {
1070
+ constructor(private readonly temporal: TemporalService) {}
1071
+
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
+ );
1081
+
1082
+ return result.result;
1083
+ }
1084
+
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
+ );
731
1100
  }
732
1101
  }
733
1102
  ```
734
1103
 
735
- ### Schedule Management
1104
+ ### Example 2: Scheduled Reports
1105
+
1106
+ Creating and managing scheduled workflows:
736
1107
 
737
1108
  ```typescript
1109
+ // report.activity.ts
738
1110
  @Injectable()
739
- export class ScheduleManagementService {
740
- constructor(private readonly temporal: TemporalService) {}
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
+ }
741
1123
 
742
- async pauseSchedule(scheduleId: string) {
743
- await this.temporal.pauseSchedule(scheduleId, 'Maintenance mode');
1124
+ @ActivityMethod('uploadReport')
1125
+ async uploadReport(reportData: ReportData): Promise<string> {
1126
+ return await this.storageService.upload(reportData);
744
1127
  }
745
1128
 
746
- async resumeSchedule(scheduleId: string) {
747
- await this.temporal.resumeSchedule(scheduleId);
1129
+ @ActivityMethod('notifyStakeholders')
1130
+ async notifyStakeholders(reportUrl: string, recipients: string[]): Promise<void> {
1131
+ await this.notificationService.send(recipients, reportUrl);
748
1132
  }
1133
+ }
749
1134
 
750
- async triggerScheduleNow(scheduleId: string) {
751
- await this.temporal.triggerSchedule(scheduleId);
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
+ });
752
1189
  }
753
1190
 
754
- async getScheduleInfo(scheduleId: string) {
755
- return this.temporal.getScheduleInfo(scheduleId);
1191
+ async deleteSchedule(scheduleId: string) {
1192
+ await this.temporal.deleteSchedule(scheduleId);
1193
+ }
1194
+
1195
+ async listAllSchedules() {
1196
+ return await this.temporal.listSchedules();
756
1197
  }
757
1198
  }
758
1199
  ```
759
1200
 
760
- ### Workflow Signals and Queries
1201
+ ## Advanced Usage
1202
+
1203
+ ### Activity Retry Configuration
1204
+
1205
+ Configure custom retry policies for different activity types:
761
1206
 
762
1207
  ```typescript
763
- // In your workflow
764
- import { defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
1208
+ // workflow.ts
1209
+ const paymentActivities = proxyActivities<typeof PaymentActivity.prototype>({
1210
+ startToCloseTimeout: '5m',
1211
+ retry: {
1212
+ maximumAttempts: 5,
1213
+ initialInterval: '1s',
1214
+ maximumInterval: '1m',
1215
+ backoffCoefficient: 2,
1216
+ nonRetryableErrorTypes: ['InvalidPaymentMethod', 'InsufficientFunds'],
1217
+ },
1218
+ });
765
1219
 
766
- export const cancelSignal = defineSignal('cancel');
767
- export const getStatusQuery = defineQuery<string>('getStatus');
1220
+ const emailActivities = proxyActivities<typeof EmailActivity.prototype>({
1221
+ startToCloseTimeout: '2m',
1222
+ retry: {
1223
+ maximumAttempts: 3,
1224
+ initialInterval: '500ms',
1225
+ },
1226
+ });
1227
+ ```
768
1228
 
769
- export async function orderWorkflow(orderData: any) {
770
- let status = 'processing';
771
- let cancelled = false;
1229
+ ### Workflow Testing
772
1230
 
773
- // Handle cancel signal
774
- setHandler(cancelSignal, () => {
775
- cancelled = true;
776
- status = 'cancelled';
1231
+ Test workflows using Temporal's testing framework:
1232
+
1233
+ ```typescript
1234
+ import { TestWorkflowEnvironment } from '@temporalio/testing';
1235
+ import { Worker } from '@temporalio/worker';
1236
+ import { processOrderWorkflow } from './order.workflow';
1237
+ import { OrderActivity } from './order.activity';
1238
+
1239
+ describe('Order Workflow', () => {
1240
+ let testEnv: TestWorkflowEnvironment;
1241
+
1242
+ beforeAll(async () => {
1243
+ testEnv = await TestWorkflowEnvironment.createTimeSkipping();
777
1244
  });
778
1245
 
779
- // Handle status query
780
- setHandler(getStatusQuery, () => status);
1246
+ afterAll(async () => {
1247
+ await testEnv?.teardown();
1248
+ });
1249
+
1250
+ it('should process order successfully', async () => {
1251
+ const { client, nativeConnection } = testEnv;
1252
+
1253
+ // Mock activities
1254
+ const mockOrderActivity = {
1255
+ validatePayment: async () => ({ valid: true }),
1256
+ reserveInventory: async () => ({ reservationId: 'res-123' }),
1257
+ chargePayment: async () => ({ transactionId: 'txn-123' }),
1258
+ sendConfirmationEmail: async () => {},
1259
+ };
1260
+
1261
+ const worker = await Worker.create({
1262
+ connection: nativeConnection,
1263
+ taskQueue: 'test',
1264
+ workflowsPath: require.resolve('./order.workflow'),
1265
+ activities: mockOrderActivity,
1266
+ });
1267
+
1268
+ await worker.runUntil(async () => {
1269
+ const result = await client.workflow.execute(processOrderWorkflow, {
1270
+ workflowId: 'test-order-1',
1271
+ taskQueue: 'test',
1272
+ args: [{
1273
+ orderId: 'order-123',
1274
+ payment: { amount: 100, currency: 'USD' },
1275
+ items: [{ id: '1', quantity: 1 }],
1276
+ }],
1277
+ });
1278
+
1279
+ expect(result.status).toBe('completed');
1280
+ expect(result.transactionId).toBe('txn-123');
1281
+ });
1282
+ });
1283
+ });
1284
+ ```
1285
+
1286
+ ### Child Workflows
1287
+
1288
+ Organize complex workflows using child workflows:
781
1289
 
782
- // Workflow logic with cancellation support
783
- if (cancelled) return;
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);
784
1328
 
785
- // Process order...
786
- status = 'completed';
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
+ }
787
1337
  }
788
1338
  ```
789
1339
 
790
- ## 🌐 Temporal Cloud Integration
1340
+ ### Custom Error Handling
791
1341
 
792
- For Temporal Cloud deployments:
1342
+ Implement custom error types and handling:
793
1343
 
794
1344
  ```typescript
795
- TemporalModule.register({
796
- connection: {
797
- address: 'your-namespace.account.tmprl.cloud:7233',
798
- namespace: 'your-namespace.account',
799
- tls: true,
800
- apiKey: process.env.TEMPORAL_API_KEY,
801
- metadata: {
802
- 'temporal-namespace': 'your-namespace.account'
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');
803
1369
  }
804
- },
805
- taskQueue: 'production-queue',
806
- worker: {
807
- workflowBundle: require('../workflows/bundle'),
808
- workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
1370
+ throw error;
809
1371
  }
810
- })
1372
+ }
1373
+
1374
+ // workflow configuration
1375
+ const activities = proxyActivities<typeof DataActivity.prototype>({
1376
+ startToCloseTimeout: '5m',
1377
+ retry: {
1378
+ nonRetryableErrorTypes: ['NonRetryableError'],
1379
+ },
1380
+ });
811
1381
  ```
812
1382
 
813
- ## 📋 Best Practices
1383
+ ## Best Practices
814
1384
 
815
- ### 1. **Activity Design**
816
- - Keep activities idempotent
817
- - Use proper timeouts and retry policies
818
- - Handle errors gracefully
819
- - Use dependency injection for testability
1385
+ ### 1. Workflow Design
820
1386
 
821
- ### 2. **Workflow Organization**
822
- - Separate workflow files from activities
823
- - Use TypeScript for type safety
824
- - Keep workflows deterministic
825
- - Bundle workflows for production
1387
+ **✅ DO:**
1388
+ - Keep workflows deterministic (no random numbers, current time, network calls)
1389
+ - Use activities for any non-deterministic operations
1390
+ - Keep workflow history size manageable (use continue-as-new for long-running workflows)
1391
+ - Export workflow functions (not classes)
1392
+ - Use `defineSignal` and `defineQuery` at module level
826
1393
 
827
- ### 3. **Configuration Management**
828
- - Use environment variables for connection settings
829
- - Separate configs for different environments
830
- - Use async configuration for dynamic settings
831
- - Validate configuration at startup
1394
+ **❌ DON'T:**
1395
+ - Don't use `@Injectable()` on workflow functions
1396
+ - Don't inject NestJS services in workflows
1397
+ - Don't use `Math.random()` or `Date.now()` directly in workflows
1398
+ - Don't make HTTP calls or database queries directly in workflows
832
1399
 
833
- ### 4. **Monitoring & Observability**
834
- - Implement health checks
835
- - Monitor worker status
836
- - Track schedule execution
837
- - Use structured logging
1400
+ ### 2. Activity Design
838
1401
 
839
- ### 5. **Production Deployment**
840
- - Use pre-bundled workflows
841
- - Configure appropriate worker limits
842
- - Enable TLS for security
843
- - Implement graceful shutdowns
1402
+ **✅ DO:**
1403
+ - Make activities idempotent (safe to retry)
1404
+ - Use `@Injectable()` and leverage NestJS DI
1405
+ - Use `@Activity()` and `@ActivityMethod()` decorators
1406
+ - Handle errors appropriately
1407
+ - Log activity execution for debugging
844
1408
 
845
- ## 📚 API Reference
1409
+ **❌ DON'T:**
1410
+ - Don't make activities too granular (network overhead)
1411
+ - Don't rely on activity execution order guarantees
1412
+ - Don't share mutable state between activity invocations
846
1413
 
847
- ### Core Decorators
1414
+ ### 3. Configuration
848
1415
 
849
- #### Activity Decorators
850
- - `@Activity(options?)` - Mark a class as containing Temporal activities
851
- - `@ActivityMethod(nameOrOptions?)` - Define an activity method with optional configuration
1416
+ **✅ DO:**
1417
+ - Use async configuration for environment-based setup
1418
+ - Configure appropriate timeouts for your use case
1419
+ - Set up proper retry policies
1420
+ - Enable graceful shutdown hooks
1421
+ - Use task queues to organize work
852
1422
 
853
- #### Scheduling Decorators
854
- - `@Scheduled(options)` - Schedule a workflow with comprehensive options
855
- - `@Cron(expression, options?)` - Schedule using cron expression
856
- - `@Interval(interval, options?)` - Schedule using interval expression
1423
+ **❌ DON'T:**
1424
+ - Don't hardcode connection strings
1425
+ - Don't use the same task queue for all workflows
1426
+ - Don't ignore timeout configurations
857
1427
 
858
- #### Workflow Decorators
859
- - `@Signal(nameOrOptions?)` - Mark a method as a signal handler
860
- - `@Query(nameOrOptions?)` - Mark a method as a query handler
1428
+ ### 4. Error Handling
861
1429
 
862
- #### Parameter Injection Decorators
863
- - `@WorkflowParam(index?)` - Extract workflow parameters
864
- - `@WorkflowContext()` - Inject workflow execution context
865
- - `@WorkflowId()` - Inject workflow ID
866
- - `@RunId()` - Inject run ID
867
- - `@TaskQueue()` - Inject task queue name
1430
+ **✅ DO:**
1431
+ - Implement compensation logic in workflows
1432
+ - Use appropriate retry policies
1433
+ - Log errors with context
1434
+ - Define non-retryable error types
1435
+ - Handle activity failures gracefully
868
1436
 
869
- ### Core Services
1437
+ **❌ DON'T:**
1438
+ - Don't swallow errors silently
1439
+ - Don't retry indefinitely
1440
+ - Don't ignore business-level failures
870
1441
 
871
- - `TemporalService` - Main unified service for all Temporal operations
872
- - `TemporalClientService` - Client-only operations (starting workflows, signals, queries)
873
- - `TemporalActivityService` - Activity discovery and management
874
- - `TemporalSchedulesService` - Schedule creation and management
875
- - `TemporalWorkerManagerService` - Worker lifecycle and health monitoring
1442
+ ### 5. Testing
876
1443
 
877
- ### Utility Functions
1444
+ **✅ DO:**
1445
+ - Write unit tests for activities
1446
+ - Use TestWorkflowEnvironment for integration tests
1447
+ - Mock external dependencies
1448
+ - Test failure scenarios
1449
+ - Test signal and query handlers
878
1450
 
879
- #### Validation
880
- - `isValidCronExpression(cron: string): boolean` - Validate cron format
881
- - `isValidIntervalExpression(interval: string): boolean` - Validate interval format
1451
+ **❌ DON'T:**
1452
+ - Don't skip workflow testing
1453
+ - Don't test against production Temporal server
1454
+ - Don't assume workflows are correct without testing
882
1455
 
883
- #### Metadata
884
- - `isActivity(target: object): boolean` - Check if class is an activity
885
- - `getActivityMetadata(target: object)` - Get activity metadata
886
- - `isActivityMethod(target: object): boolean` - Check if method is activity method
887
- - `getActivityMethodMetadata(target: object)` - Get activity method metadata
888
- - `getParameterMetadata(target: object, propertyKey: string | symbol)` - Get parameter metadata
1456
+ ## Health Monitoring
889
1457
 
890
- #### Logging
891
- - `TemporalLogger` - Enhanced logger with context support
892
- - `TemporalLoggerManager` - Global logger configuration
1458
+ The package includes comprehensive health monitoring capabilities for production deployments.
893
1459
 
894
- ### Predefined Constants
1460
+ ### Using Built-in Health Module
895
1461
 
896
- #### Schedule Expressions
897
- - `CRON_EXPRESSIONS` - Common cron patterns (DAILY_8AM, WEEKLY_MONDAY_9AM, etc.)
898
- - `INTERVAL_EXPRESSIONS` - Common interval patterns (EVERY_5_MINUTES, EVERY_HOUR, etc.)
1462
+ ```typescript
1463
+ // app.module.ts
1464
+ import { Module } from '@nestjs/common';
1465
+ import { TemporalModule } from 'nestjs-temporal-core';
1466
+ import { TemporalHealthModule } from 'nestjs-temporal-core/health';
899
1467
 
900
- #### Configuration Presets
901
- - `TIMEOUTS` - Common timeout values for different operation types
902
- - `RETRY_POLICIES` - Predefined retry policies (QUICK, STANDARD, AGGRESSIVE)
1468
+ @Module({
1469
+ imports: [
1470
+ TemporalModule.register({
1471
+ connection: { address: 'localhost:7233' },
1472
+ taskQueue: 'my-queue',
1473
+ worker: {
1474
+ workflowsPath: require.resolve('./workflows'),
1475
+ activityClasses: [MyActivity],
1476
+ },
1477
+ }),
1478
+ TemporalHealthModule, // Adds /health/temporal endpoint
1479
+ ],
1480
+ })
1481
+ export class AppModule {}
1482
+ ```
903
1483
 
904
- #### Module Tokens
905
- - `TEMPORAL_MODULE_OPTIONS` - Main module configuration token
906
- - `TEMPORAL_CLIENT` - Client instance injection token
907
- - `TEMPORAL_CONNECTION` - Connection instance injection token
1484
+ ### Custom Health Checks
908
1485
 
909
- ## 🤝 Contributing
1486
+ ```typescript
1487
+ @Controller('health')
1488
+ export class HealthController {
1489
+ constructor(private readonly temporal: TemporalService) {}
910
1490
 
911
- Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
1491
+ @Get('/status')
1492
+ async getHealthStatus() {
1493
+ const health = this.temporal.getHealth();
1494
+
1495
+ return {
1496
+ status: health.overallHealth,
1497
+ timestamp: new Date(),
1498
+ services: {
1499
+ client: {
1500
+ healthy: health.client.status === 'healthy',
1501
+ connection: health.client.connectionStatus,
1502
+ },
1503
+ worker: {
1504
+ healthy: health.worker.status === 'healthy',
1505
+ state: health.worker.state,
1506
+ activitiesRegistered: health.worker.activitiesCount,
1507
+ },
1508
+ discovery: {
1509
+ healthy: health.discovery.status === 'healthy',
1510
+ activitiesDiscovered: health.discovery.activitiesDiscovered,
1511
+ },
1512
+ },
1513
+ uptime: health.uptime,
1514
+ };
1515
+ }
912
1516
 
913
- ## 📄 License
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
+ ```
914
1533
 
915
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1534
+ ### Health Check Response
916
1535
 
917
- ## 🙏 Acknowledgments
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
+ }
1555
+ ```
1556
+ ```
918
1557
 
919
- - [Temporal.io](https://temporal.io/) for the amazing workflow engine
920
- - [NestJS](https://nestjs.com/) for the fantastic framework
921
- - The TypeScript community for excellent tooling
1558
+ ## Migration Guide
922
1559
 
923
- ---
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
924
1581
 
925
- Built with ❤️ for the NestJS and Temporal communities
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;
926
1588
 
927
- ## Workflow Implementation Approaches
1589
+ constructor(private temporal: TemporalService) {}
928
1590
 
929
- NestJS Temporal Core supports two main ways to define workflows:
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!
930
1595
 
931
- ### 1. Function-Based Workflows (Recommended for Most Use Cases)
932
- - **How:** Export a plain async function from your workflow file.
933
- - **Benefits:**
934
- - Simpler, more idiomatic Temporal style
935
- - Fully compatible with Temporal's TypeScript SDK
936
- - Easier to test and bundle
937
- - **When to Use:**
938
- - Most workflows, especially if you don't need dependency injection or advanced metadata
1596
+ // Manual worker creation...
1597
+ }
1598
+ }
1599
+ ```
939
1600
 
1601
+ **After (v3.0.12):**
940
1602
  ```typescript
941
- // workflows/email.workflow.ts
942
- export async function processEmailWorkflow(userId: string, emailData: { to: string; subject: string; body: string }) {
943
- // ...
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
+ }
944
1661
  }
945
1662
  ```
946
1663
 
947
- ### 2. Class-Based Workflows with Decorators (Advanced)
948
- - **How:** Use an injectable class and parameter decorators like `@WorkflowParam`, `@WorkflowId`, etc.
949
- - **Benefits:**
950
- - Enables parameter injection (workflowId, context, etc.)
951
- - Useful for advanced scenarios (e.g., dynamic metadata, dependency injection)
952
- - Can organize signals/queries as class methods
953
- - **When to Use:**
954
- - When you need to access workflow context, IDs, or inject dependencies
955
- - When you want to group signals/queries with workflow logic
1664
+ #### New APIs in v3.0.12
956
1665
 
957
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
958
1737
  @Injectable()
959
- export class OrderWorkflowController {
960
- async processOrder(
961
- @WorkflowParam(0) orderId: string,
962
- @WorkflowId() workflowId: string,
963
- @WorkflowContext() context: any
964
- ) {
965
- // ...
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);
966
1756
  }
967
1757
  }
968
1758
  ```
969
1759
 
970
- #### Which Should I Use?
971
- - **Start with function-based workflows** for simplicity and compatibility.
972
- - **Use class-based workflows** only if you need advanced features like parameter injection or grouping signals/queries.
973
- - `@WorkflowParam` and related decorators are only needed for class-based workflows and provide access to workflow metadata or injected parameters.
1760
+ ## Troubleshooting
1761
+
1762
+ ### Common Issues and Solutions
1763
+
1764
+ #### 1. Connection Errors
1765
+
1766
+ **Problem:** Cannot connect to Temporal server
1767
+
1768
+ ```
1769
+ Error: Failed to connect to localhost:7233
1770
+ ```
1771
+
1772
+ **Solutions:**
1773
+ ```typescript
1774
+ // Check connection configuration
1775
+ const health = temporalService.getHealth();
1776
+ console.log('Connection status:', health.client.connectionStatus);
1777
+
1778
+ // Verify Temporal server is running
1779
+ // docker ps | grep temporal
1780
+
1781
+ // Check connection settings
1782
+ TemporalModule.register({
1783
+ connection: {
1784
+ address: process.env.TEMPORAL_ADDRESS || 'localhost:7233',
1785
+ namespace: 'default',
1786
+ },
1787
+ })
1788
+ ```
1789
+
1790
+ #### 2. Activity Not Found
1791
+
1792
+ **Problem:** Workflow cannot find registered activities
1793
+
1794
+ ```
1795
+ Error: Activity 'myActivity' not found
1796
+ ```
1797
+
1798
+ **Solutions:**
1799
+ ```typescript
1800
+ // 1. Ensure activity is in activityClasses array
1801
+ TemporalModule.register({
1802
+ worker: {
1803
+ activityClasses: [MyActivity], // Must include the activity class
1804
+ },
1805
+ })
1806
+
1807
+ // 2. Verify activity is registered as provider
1808
+ @Module({
1809
+ providers: [MyActivity], // Must be in providers array
1810
+ })
1811
+
1812
+ // 3. Check activity decorator
1813
+ @Activity({ name: 'my-activities' })
1814
+ export class MyActivity {
1815
+ @ActivityMethod('myActivity')
1816
+ async myActivity() { }
1817
+ }
1818
+
1819
+ // 4. Check discovery status
1820
+ const health = temporalService.getHealth();
1821
+ console.log('Activities discovered:', health.discovery.activitiesDiscovered);
1822
+ ```
1823
+
1824
+ #### 3. Workflow Registration Issues
1825
+
1826
+ **Problem:** Workflow not found or not executing
1827
+
1828
+ ```
1829
+ Error: Workflow 'myWorkflow' not found
1830
+ ```
1831
+
1832
+ **Solutions:**
1833
+ ```typescript
1834
+ // 1. Ensure workflowsPath is correct
1835
+ TemporalModule.register({
1836
+ worker: {
1837
+ workflowsPath: require.resolve('./workflows'), // Must resolve to workflows file/directory
1838
+ },
1839
+ })
1840
+
1841
+ // 2. Export workflow function properly
1842
+ // workflows/index.ts
1843
+ export { processOrderWorkflow } from './order.workflow';
1844
+ export { reportWorkflow } from './report.workflow';
1845
+
1846
+ // 3. Use correct workflow name when starting
1847
+ await temporal.startWorkflow(
1848
+ 'processOrderWorkflow', // Must match exported function name
1849
+ [args],
1850
+ options
1851
+ );
1852
+ ```
1853
+
1854
+ #### 4. Timeout Issues
1855
+
1856
+ **Problem:** Activities or workflows timing out
1857
+
1858
+ ```
1859
+ Error: Activity timed out after 10s
1860
+ ```
1861
+
1862
+ **Solutions:**
1863
+ ```typescript
1864
+ // Configure appropriate timeouts
1865
+ const activities = proxyActivities<typeof MyActivity.prototype>({
1866
+ startToCloseTimeout: '10m', // Increase for long-running activities
1867
+ scheduleToCloseTimeout: '15m', // Total time including queuing
1868
+ scheduleToStartTimeout: '5m', // Time waiting in queue
1869
+ });
1870
+
1871
+ // For workflows
1872
+ await temporal.startWorkflow('myWorkflow', [args], {
1873
+ workflowExecutionTimeout: '24h', // Max total execution time
1874
+ workflowRunTimeout: '12h', // Max single run time
1875
+ workflowTaskTimeout: '10s', // Decision task timeout
1876
+ });
1877
+ ```
1878
+
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
+ ### Debug Mode
1941
+
1942
+ Enable comprehensive debugging:
1943
+
1944
+ ```typescript
1945
+ TemporalModule.register({
1946
+ logLevel: 'debug',
1947
+ enableLogger: true,
1948
+ connection: {
1949
+ address: 'localhost:7233',
1950
+ },
1951
+ worker: {
1952
+ debugMode: true, // If available
1953
+ },
1954
+ })
1955
+
1956
+ // Check detailed health and statistics
1957
+ const health = temporalService.getHealth();
1958
+ const stats = temporalService.getStatistics();
1959
+ console.log('Health:', JSON.stringify(health, null, 2));
1960
+ console.log('Stats:', JSON.stringify(stats, null, 2));
1961
+ ```
1962
+
1963
+ ### Getting Help
1964
+
1965
+ If you're still experiencing issues:
1966
+
1967
+ 1. **Check the logs** - Enable debug logging to see detailed information
1968
+ 2. **Verify configuration** - Double-check all connection and worker settings
1969
+ 3. **Test connectivity** - Ensure Temporal server is accessible
1970
+ 4. **Review health status** - Use `getHealth()` to identify failing components
1971
+ 5. **Check GitHub Issues** - [Search existing issues](https://github.com/harsh-simform/nestjs-temporal-core/issues)
1972
+ 6. **Create an issue** - Provide logs, configuration, and minimal reproduction
1973
+
1974
+ ## Requirements
1975
+
1976
+ - **Node.js**: >= 16.0.0
1977
+ - **NestJS**: >= 9.0.0
1978
+ - **Temporal Server**: >= 1.20.0
1979
+
1980
+ ## Contributing
1981
+
1982
+ We welcome contributions! To contribute:
1983
+
1984
+ 1. Fork the repository
1985
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1986
+ 3. Make your changes
1987
+ 4. Run tests (`npm test`)
1988
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
1989
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
1990
+ 7. Open a Pull Request
1991
+
1992
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
1993
+
1994
+ ### Development Setup
1995
+
1996
+ ```bash
1997
+ # Clone the repository
1998
+ git clone https://github.com/harsh-simform/nestjs-temporal-core.git
1999
+ cd nestjs-temporal-core
2000
+
2001
+ # Install dependencies
2002
+ npm install
2003
+
2004
+ # Run tests
2005
+ npm test
2006
+
2007
+ # Run tests with coverage
2008
+ npm run test:cov
2009
+
2010
+ # Build the package
2011
+ npm run build
2012
+
2013
+ # Generate documentation
2014
+ npm run docs:generate
2015
+ ```
2016
+
2017
+ ## License
2018
+
2019
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
2020
+
2021
+ ## Support and Resources
2022
+
2023
+ - 📚 **Documentation**: [Full API Documentation](https://harsh-simform.github.io/nestjs-temporal-core/)
2024
+ - 🐛 **Issues**: [GitHub Issues](https://github.com/harsh-simform/nestjs-temporal-core/issues)
2025
+ - 💬 **Discussions**: [GitHub Discussions](https://github.com/harsh-simform/nestjs-temporal-core/discussions)
2026
+ - 📦 **NPM**: [nestjs-temporal-core](https://www.npmjs.com/package/nestjs-temporal-core)
2027
+ - 🔄 **Changelog**: [Releases](https://github.com/harsh-simform/nestjs-temporal-core/releases)
2028
+ - 📖 **Example Project**: [nestjs-temporal-core-example](https://github.com/harsh-simform/nestjs-temporal-core-example)
2029
+
2030
+ ## Related Projects
2031
+
2032
+ - [Temporal.io](https://temporal.io/) - The underlying workflow orchestration platform
2033
+ - [NestJS](https://nestjs.com/) - Progressive Node.js framework
2034
+ - [@temporalio/sdk](https://www.npmjs.com/package/@temporalio/client) - Official Temporal TypeScript SDK
974
2035
 
975
2036
  ---
2037
+