nestjs-temporal-core 3.0.10 → 3.0.11

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 (122) hide show
  1. package/README.md +1451 -721
  2. package/dist/constants.d.ts +49 -151
  3. package/dist/constants.js +38 -141
  4. package/dist/constants.js.map +1 -1
  5. package/dist/decorators/activity.decorator.js +75 -15
  6. package/dist/decorators/activity.decorator.js.map +1 -1
  7. package/dist/decorators/index.d.ts +1 -3
  8. package/dist/decorators/index.js +4 -13
  9. package/dist/decorators/index.js.map +1 -1
  10. package/dist/decorators/workflow.decorator.d.ts +7 -3
  11. package/dist/decorators/workflow.decorator.js +161 -48
  12. package/dist/decorators/workflow.decorator.js.map +1 -1
  13. package/dist/health/temporal-health.controller.d.ts +7 -0
  14. package/dist/health/temporal-health.controller.js +77 -0
  15. package/dist/health/temporal-health.controller.js.map +1 -0
  16. package/dist/health/temporal-health.module.d.ts +2 -0
  17. package/dist/health/temporal-health.module.js +20 -0
  18. package/dist/health/temporal-health.module.js.map +1 -0
  19. package/dist/index.d.ts +10 -20
  20. package/dist/index.js +15 -30
  21. package/dist/index.js.map +1 -1
  22. package/dist/interfaces.d.ts +740 -160
  23. package/dist/interfaces.js +1 -2
  24. package/dist/interfaces.js.map +1 -1
  25. package/dist/providers/temporal-connection.factory.d.ts +28 -0
  26. package/dist/providers/temporal-connection.factory.js +194 -0
  27. package/dist/providers/temporal-connection.factory.js.map +1 -0
  28. package/dist/services/temporal-client.service.d.ts +33 -0
  29. package/dist/services/temporal-client.service.js +285 -0
  30. package/dist/services/temporal-client.service.js.map +1 -0
  31. package/dist/services/temporal-discovery.service.d.ts +34 -0
  32. package/dist/services/temporal-discovery.service.js +348 -0
  33. package/dist/services/temporal-discovery.service.js.map +1 -0
  34. package/dist/services/temporal-metadata.service.d.ts +37 -0
  35. package/dist/services/temporal-metadata.service.js +512 -0
  36. package/dist/services/temporal-metadata.service.js.map +1 -0
  37. package/dist/services/temporal-schedule.service.d.ts +35 -0
  38. package/dist/services/temporal-schedule.service.js +380 -0
  39. package/dist/services/temporal-schedule.service.js.map +1 -0
  40. package/dist/services/temporal-worker.service.d.ts +54 -0
  41. package/dist/services/temporal-worker.service.js +605 -0
  42. package/dist/services/temporal-worker.service.js.map +1 -0
  43. package/dist/services/temporal.service.d.ts +85 -0
  44. package/dist/services/temporal.service.js +572 -0
  45. package/dist/services/temporal.service.js.map +1 -0
  46. package/dist/temporal.module.d.ts +6 -9
  47. package/dist/temporal.module.js +160 -109
  48. package/dist/temporal.module.js.map +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/dist/utils/index.d.ts +2 -2
  51. package/dist/utils/index.js +5 -8
  52. package/dist/utils/index.js.map +1 -1
  53. package/dist/utils/logger.d.ts +10 -4
  54. package/dist/utils/logger.js +77 -106
  55. package/dist/utils/logger.js.map +1 -1
  56. package/dist/utils/metadata.d.ts +1 -3
  57. package/dist/utils/metadata.js +8 -18
  58. package/dist/utils/metadata.js.map +1 -1
  59. package/dist/utils/validation.d.ts +16 -2
  60. package/dist/utils/validation.js +103 -9
  61. package/dist/utils/validation.js.map +1 -1
  62. package/package.json +37 -26
  63. package/dist/activity/index.d.ts +0 -2
  64. package/dist/activity/index.js +0 -19
  65. package/dist/activity/index.js.map +0 -1
  66. package/dist/activity/temporal-activity.module.d.ts +0 -11
  67. package/dist/activity/temporal-activity.module.js +0 -52
  68. package/dist/activity/temporal-activity.module.js.map +0 -1
  69. package/dist/activity/temporal-activity.service.d.ts +0 -46
  70. package/dist/activity/temporal-activity.service.js +0 -192
  71. package/dist/activity/temporal-activity.service.js.map +0 -1
  72. package/dist/client/index.d.ts +0 -3
  73. package/dist/client/index.js +0 -20
  74. package/dist/client/index.js.map +0 -1
  75. package/dist/client/temporal-client.module.d.ts +0 -18
  76. package/dist/client/temporal-client.module.js +0 -198
  77. package/dist/client/temporal-client.module.js.map +0 -1
  78. package/dist/client/temporal-client.service.d.ts +0 -35
  79. package/dist/client/temporal-client.service.js +0 -187
  80. package/dist/client/temporal-client.service.js.map +0 -1
  81. package/dist/client/temporal-schedule.service.d.ts +0 -41
  82. package/dist/client/temporal-schedule.service.js +0 -204
  83. package/dist/client/temporal-schedule.service.js.map +0 -1
  84. package/dist/decorators/parameter.decorator.d.ts +0 -5
  85. package/dist/decorators/parameter.decorator.js +0 -57
  86. package/dist/decorators/parameter.decorator.js.map +0 -1
  87. package/dist/decorators/scheduling.decorator.d.ts +0 -4
  88. package/dist/decorators/scheduling.decorator.js +0 -44
  89. package/dist/decorators/scheduling.decorator.js.map +0 -1
  90. package/dist/discovery/index.d.ts +0 -2
  91. package/dist/discovery/index.js +0 -19
  92. package/dist/discovery/index.js.map +0 -1
  93. package/dist/discovery/temporal-discovery.service.d.ts +0 -39
  94. package/dist/discovery/temporal-discovery.service.js +0 -191
  95. package/dist/discovery/temporal-discovery.service.js.map +0 -1
  96. package/dist/discovery/temporal-schedule-manager.service.d.ts +0 -41
  97. package/dist/discovery/temporal-schedule-manager.service.js +0 -238
  98. package/dist/discovery/temporal-schedule-manager.service.js.map +0 -1
  99. package/dist/schedules/index.d.ts +0 -2
  100. package/dist/schedules/index.js +0 -19
  101. package/dist/schedules/index.js.map +0 -1
  102. package/dist/schedules/temporal-schedules.module.d.ts +0 -11
  103. package/dist/schedules/temporal-schedules.module.js +0 -55
  104. package/dist/schedules/temporal-schedules.module.js.map +0 -1
  105. package/dist/schedules/temporal-schedules.service.d.ts +0 -52
  106. package/dist/schedules/temporal-schedules.service.js +0 -221
  107. package/dist/schedules/temporal-schedules.service.js.map +0 -1
  108. package/dist/temporal.service.d.ts +0 -77
  109. package/dist/temporal.service.js +0 -243
  110. package/dist/temporal.service.js.map +0 -1
  111. package/dist/worker/index.d.ts +0 -3
  112. package/dist/worker/index.js +0 -20
  113. package/dist/worker/index.js.map +0 -1
  114. package/dist/worker/temporal-metadata.accessor.d.ts +0 -32
  115. package/dist/worker/temporal-metadata.accessor.js +0 -208
  116. package/dist/worker/temporal-metadata.accessor.js.map +0 -1
  117. package/dist/worker/temporal-worker-manager.service.d.ts +0 -49
  118. package/dist/worker/temporal-worker-manager.service.js +0 -389
  119. package/dist/worker/temporal-worker-manager.service.js.map +0 -1
  120. package/dist/worker/temporal-worker.module.d.ts +0 -18
  121. package/dist/worker/temporal-worker.module.js +0 -156
  122. 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,1603 @@ 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.
250
-
251
- > **Tip:** In Temporal workflows, use `condition()` or similar mechanisms to wait for signals in a non-blocking, replay-safe way.
215
+ ### 5. Use in Services
252
216
 
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
393
-
394
- ### Client-Only Integration
287
+ ### 2. Client-Only Module
395
288
 
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
- ```
506
-
507
- ### Environment-Specific Configurations
508
-
509
- ```typescript
510
- // Development
511
- const developmentConfig = {
512
- connection: {
513
- address: 'localhost:7233',
514
- namespace: 'development'
515
- },
516
- taskQueue: 'dev-queue',
517
- 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
530
- },
531
- taskQueue: process.env.TEMPORAL_TASK_QUEUE!,
532
- worker: {
533
- workflowBundle: require('../workflows/bundle'), // Pre-bundled
534
- workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
535
- }
536
- };
537
339
  ```
538
340
 
539
- ## 📝 Logger Configuration
341
+ ## Configuration
540
342
 
541
- Control logging behavior across all Temporal modules with configurable logger settings:
343
+ ## Configuration
542
344
 
543
- ### Basic Logger Setup
345
+ ### Basic Configuration
544
346
 
545
347
  ```typescript
546
- // Enable/disable logging and set log levels
547
348
  TemporalModule.register({
548
349
  connection: {
549
350
  address: 'localhost:7233',
550
- namespace: 'default'
351
+ namespace: 'default',
551
352
  },
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'
353
+ taskQueue: 'my-task-queue',
556
354
  worker: {
557
- workflowsPath: './dist/workflows',
558
- activityClasses: [EmailActivities]
559
- }
560
- })
561
- ```
562
-
563
- ### Environment-Based Logger Configuration
564
-
565
- ```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
355
+ workflowsPath: require.resolve('./workflows'),
356
+ activityClasses: [PaymentActivity, EmailActivity],
357
+ autoStart: true,
358
+ maxConcurrentActivityExecutions: 100,
571
359
  },
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
578
- }
579
- };
580
-
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'
587
- }
360
+ logLevel: 'info',
361
+ enableLogger: true,
588
362
  })
589
363
  ```
590
364
 
591
- ### Individual Module Logger Configuration
365
+ ### Async Configuration
592
366
 
593
- Configure logging for specific modules:
367
+ For dynamic configuration using environment variables or config services:
594
368
 
595
369
  ```typescript
596
- // Activity Module with custom logging
597
- TemporalActivityModule.forRoot({
598
- activityClasses: [EmailActivities],
599
- enableLogger: true,
600
- logLevel: 'debug'
601
- })
602
-
603
- // Schedules Module with minimal logging
604
- TemporalSchedulesModule.forRoot({
605
- autoStart: true,
606
- enableLogger: true,
607
- logLevel: 'error' // Only show errors
608
- })
370
+ // config/temporal.config.ts
371
+ import { Injectable } from '@nestjs/common';
372
+ import { ConfigService } from '@nestjs/config';
373
+ import { TemporalOptionsFactory, TemporalOptions } from 'nestjs-temporal-core';
609
374
 
610
- // Client Module with no logging
611
- TemporalClientModule.forRoot({
612
- connection: { address: 'localhost:7233' },
613
- enableLogger: false
614
- })
615
- ```
375
+ @Injectable()
376
+ export class TemporalConfigService implements TemporalOptionsFactory {
377
+ constructor(private configService: ConfigService) {}
616
378
 
617
- ### Log Level Hierarchy
379
+ createTemporalOptions(): TemporalOptions {
380
+ return {
381
+ connection: {
382
+ address: this.configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
383
+ namespace: this.configService.get('TEMPORAL_NAMESPACE', 'default'),
384
+ },
385
+ taskQueue: this.configService.get('TEMPORAL_TASK_QUEUE', 'default'),
386
+ worker: {
387
+ workflowsPath: require.resolve('../workflows'),
388
+ activityClasses: [], // Populated by module
389
+ maxConcurrentActivityExecutions: 100,
390
+ },
391
+ };
392
+ }
393
+ }
618
394
 
619
- The logger follows a hierarchical structure where each level includes all levels above it:
395
+ // app.module.ts
396
+ import { ConfigModule } from '@nestjs/config';
620
397
 
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
398
+ @Module({
399
+ imports: [
400
+ ConfigModule.forRoot({ isGlobal: true }),
401
+ TemporalModule.registerAsync({
402
+ imports: [ConfigModule],
403
+ useClass: TemporalConfigService,
404
+ }),
405
+ ],
406
+ })
407
+ export class AppModule {}
408
+ ```
626
409
 
627
- ### Async Configuration with Logger
410
+ ### Alternative Async Pattern (useFactory)
628
411
 
629
412
  ```typescript
630
413
  TemporalModule.registerAsync({
631
414
  imports: [ConfigModule],
632
- useFactory: (config: ConfigService) => ({
415
+ useFactory: (configService: ConfigService) => ({
633
416
  connection: {
634
- address: config.get('TEMPORAL_ADDRESS'),
635
- namespace: config.get('TEMPORAL_NAMESPACE')
417
+ address: configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
418
+ namespace: configService.get('TEMPORAL_NAMESPACE', 'default'),
636
419
  },
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'),
420
+ taskQueue: configService.get('TEMPORAL_TASK_QUEUE', 'default'),
641
421
  worker: {
642
- workflowsPath: './dist/workflows'
643
- }
422
+ workflowsPath: require.resolve('./workflows'),
423
+ activityClasses: [PaymentActivity, EmailActivity],
424
+ },
644
425
  }),
645
- inject: [ConfigService]
426
+ inject: [ConfigService],
646
427
  })
647
428
  ```
648
429
 
649
- ### Logger Examples
430
+ ### TLS Configuration (Temporal Cloud)
431
+
432
+ For secure connections to Temporal Cloud:
650
433
 
651
434
  ```typescript
652
- // Silent mode - no logs
653
- {
654
- enableLogger: false
655
- }
435
+ import * as fs from 'fs';
656
436
 
657
- // Error only - for production monitoring
658
- {
659
- enableLogger: true,
660
- logLevel: 'error'
661
- }
437
+ TemporalModule.register({
438
+ connection: {
439
+ address: 'your-namespace.your-account.tmprl.cloud:7233',
440
+ namespace: 'your-namespace.your-account',
441
+ tls: {
442
+ clientCertPair: {
443
+ crt: fs.readFileSync('/path/to/client.crt'),
444
+ key: fs.readFileSync('/path/to/client.key'),
445
+ },
446
+ },
447
+ },
448
+ taskQueue: 'my-task-queue',
449
+ worker: {
450
+ workflowsPath: require.resolve('./workflows'),
451
+ activityClasses: [PaymentActivity],
452
+ },
453
+ })
454
+ ```
662
455
 
663
- // Development mode - detailed logging
664
- {
665
- enableLogger: true,
666
- logLevel: 'debug'
667
- }
456
+ ### Configuration Options Reference
668
457
 
669
- // Verbose mode - maximum detail for troubleshooting
670
- {
671
- enableLogger: true,
672
- logLevel: 'verbose'
458
+ ```typescript
459
+ interface TemporalOptions {
460
+ // Connection settings
461
+ connection: {
462
+ address: string; // Temporal server address (default: 'localhost:7233')
463
+ namespace?: string; // Temporal namespace (default: 'default')
464
+ tls?: TLSConfig; // TLS configuration for secure connections
465
+ };
466
+
467
+ // Task queue name
468
+ taskQueue?: string; // Default task queue (default: 'default')
469
+
470
+ // Worker configuration
471
+ worker?: {
472
+ workflowsPath?: string; // Path to workflow definitions (use require.resolve)
473
+ activityClasses?: any[]; // Array of activity classes to register
474
+ autoStart?: boolean; // Auto-start worker on module init (default: true)
475
+ maxConcurrentActivityExecutions?: number; // Max concurrent activities (default: 100)
476
+ maxActivitiesPerSecond?: number; // Rate limit for activities
477
+ };
478
+
479
+ // Logging
480
+ logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error'; // Log level (default: 'info')
481
+ enableLogger?: boolean; // Enable logging (default: true)
482
+
483
+ // Advanced
484
+ isGlobal?: boolean; // Make module global (default: false)
485
+ autoRestart?: boolean; // Auto-restart worker on failure (default: true)
673
486
  }
674
487
  ```
675
488
 
676
- ## 📊 Health Monitoring
489
+ ## Core Concepts
677
490
 
678
- Built-in health monitoring for production environments:
491
+ ### Activities
679
492
 
680
- ```typescript
681
- @Controller('health')
682
- export class HealthController {
683
- constructor(private readonly temporal: TemporalService) {}
493
+ 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
494
 
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
- };
495
+ **Key Points:**
496
+ - Activities are NestJS services (`@Injectable()`)
497
+ - Use `@Activity()` decorator at class level
498
+ - Use `@ActivityMethod()` decorator for methods to be registered
499
+ - Activities should be idempotent and handle retries gracefully
500
+ - Full access to NestJS DI (inject services, repositories, etc.)
501
+
502
+ ```typescript
503
+ @Injectable()
504
+ @Activity({ name: 'order-activities' })
505
+ export class OrderActivity {
506
+ constructor(
507
+ private readonly orderRepository: OrderRepository,
508
+ private readonly emailService: EmailService,
509
+ ) {}
510
+
511
+ @ActivityMethod('createOrder')
512
+ async createOrder(orderData: CreateOrderData): Promise<Order> {
513
+ // Database operations with full DI support
514
+ const order = await this.orderRepository.create(orderData);
515
+ await this.emailService.sendConfirmation(order);
516
+ return order;
693
517
  }
694
518
 
695
- @Get('temporal/detailed')
696
- async getDetailedStatus() {
697
- const systemStatus = await this.temporal.getSystemStatus();
698
- const stats = this.temporal.getDiscoveryStats();
699
-
700
- return {
701
- system: systemStatus,
702
- discovery: stats,
703
- schedules: this.temporal.getScheduleStats()
704
- };
519
+ @ActivityMethod('validateInventory')
520
+ async validateInventory(items: OrderItem[]): Promise<boolean> {
521
+ // Business logic with injected services
522
+ return await this.orderRepository.checkInventory(items);
705
523
  }
706
524
  }
707
525
  ```
708
526
 
709
- ## 🔧 Advanced Features
527
+ ### Workflows
528
+
529
+ Workflows are **pure Temporal functions** (NOT NestJS services) that orchestrate activities. They must be deterministic and use Temporal's workflow APIs.
710
530
 
711
- ### Activity Options
531
+ **Important:** Workflows are NOT decorated with `@Injectable()` and should NOT use NestJS dependency injection.
712
532
 
713
533
  ```typescript
714
- @Activity()
715
- @Injectable()
716
- export class PaymentActivities {
717
-
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
727
- }
728
- })
729
- async processPayment(orderId: string, amount: number) {
730
- // Complex payment processing with retries
731
- }
732
- }
733
- ```
534
+ // order.workflow.ts
535
+ import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
536
+ import type { OrderActivity } from './order.activity';
537
+
538
+ // Create activity proxies with proper typing
539
+ const { createOrder, validateInventory } = proxyActivities<typeof OrderActivity.prototype>({
540
+ startToCloseTimeout: '5m',
541
+ retry: {
542
+ maximumAttempts: 3,
543
+ initialInterval: '1s',
544
+ maximumInterval: '30s',
545
+ },
546
+ });
734
547
 
735
- ### Schedule Management
548
+ // Define signals and queries at module level
549
+ export const cancelOrderSignal = defineSignal<[string]>('cancelOrder');
550
+ export const getOrderStatusQuery = defineQuery<string>('getOrderStatus');
736
551
 
737
- ```typescript
738
- @Injectable()
739
- export class ScheduleManagementService {
740
- constructor(private readonly temporal: TemporalService) {}
552
+ // Workflow function (exported, not a class)
553
+ export async function processOrderWorkflow(orderData: CreateOrderData): Promise<OrderResult> {
554
+ let status = 'pending';
741
555
 
742
- async pauseSchedule(scheduleId: string) {
743
- await this.temporal.pauseSchedule(scheduleId, 'Maintenance mode');
744
- }
556
+ // Set up signal handler
557
+ setHandler(cancelOrderSignal, (reason: string) => {
558
+ status = 'cancelled';
559
+ });
745
560
 
746
- async resumeSchedule(scheduleId: string) {
747
- await this.temporal.resumeSchedule(scheduleId);
748
- }
561
+ // Set up query handler
562
+ setHandler(getOrderStatusQuery, () => status);
749
563
 
750
- async triggerScheduleNow(scheduleId: string) {
751
- await this.temporal.triggerSchedule(scheduleId);
752
- }
564
+ try {
565
+ // Validate inventory
566
+ const isValid = await validateInventory(orderData.items);
567
+ if (!isValid) {
568
+ throw new Error('Insufficient inventory');
569
+ }
570
+
571
+ // Create order
572
+ status = 'processing';
573
+ const order = await createOrder(orderData);
574
+ status = 'completed';
753
575
 
754
- async getScheduleInfo(scheduleId: string) {
755
- return this.temporal.getScheduleInfo(scheduleId);
576
+ return {
577
+ orderId: order.id,
578
+ status,
579
+ };
580
+ } catch (error) {
581
+ status = 'failed';
582
+ throw error;
756
583
  }
757
584
  }
758
585
  ```
759
586
 
760
- ### Workflow Signals and Queries
587
+ ### Signals and Queries
588
+
589
+ Signals allow external systems to send events to workflows, while queries provide read-only access to workflow state.
761
590
 
762
591
  ```typescript
763
- // In your workflow
764
- import { defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
592
+ import { defineSignal, defineQuery, setHandler, condition } from '@temporalio/workflow';
765
593
 
766
- export const cancelSignal = defineSignal('cancel');
594
+ // Define at module level
595
+ export const updateStatusSignal = defineSignal<[string]>('updateStatus');
596
+ export const addItemSignal = defineSignal<[Item]>('addItem');
597
+ export const getItemsQuery = defineQuery<Item[]>('getItems');
767
598
  export const getStatusQuery = defineQuery<string>('getStatus');
768
599
 
769
- export async function orderWorkflow(orderData: any) {
770
- let status = 'processing';
771
- let cancelled = false;
600
+ export async function myWorkflow(): Promise<void> {
601
+ let status = 'pending';
602
+ const items: Item[] = [];
772
603
 
773
- // Handle cancel signal
774
- setHandler(cancelSignal, () => {
775
- cancelled = true;
776
- status = 'cancelled';
604
+ // Set up handlers
605
+ setHandler(updateStatusSignal, (newStatus: string) => {
606
+ status = newStatus;
607
+ });
608
+
609
+ setHandler(addItemSignal, (item: Item) => {
610
+ items.push(item);
777
611
  });
778
612
 
779
- // Handle status query
613
+ setHandler(getItemsQuery, () => items);
780
614
  setHandler(getStatusQuery, () => status);
781
615
 
782
- // Workflow logic with cancellation support
783
- if (cancelled) return;
784
-
785
- // Process order...
786
- status = 'completed';
616
+ // Wait for completion signal
617
+ await condition(() => status === 'completed');
787
618
  }
788
619
  ```
789
620
 
790
- ## 🌐 Temporal Cloud Integration
621
+ ### Using Workflows in Services
791
622
 
792
- For Temporal Cloud deployments:
623
+ Inject `TemporalService` in your NestJS services to interact with workflows:
793
624
 
794
625
  ```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'
626
+ @Injectable()
627
+ export class OrderService {
628
+ constructor(private readonly temporal: TemporalService) {}
629
+
630
+ async createOrder(orderData: CreateOrderData) {
631
+ // Start workflow - note the method signature
632
+ const result = await this.temporal.startWorkflow(
633
+ 'processOrderWorkflow', // Workflow function name
634
+ [orderData], // Arguments array
635
+ { // Options
636
+ workflowId: `order-${Date.now()}`,
637
+ taskQueue: 'order-queue',
638
+ }
639
+ );
640
+
641
+ return {
642
+ workflowId: result.result.workflowId,
643
+ runId: result.result.runId,
644
+ };
645
+ }
646
+
647
+ async queryOrderStatus(workflowId: string) {
648
+ const result = await this.temporal.queryWorkflow(
649
+ workflowId,
650
+ 'getOrderStatus'
651
+ );
652
+
653
+ return result.result;
654
+ }
655
+
656
+ async cancelOrder(workflowId: string, reason: string) {
657
+ await this.temporal.signalWorkflow(
658
+ workflowId,
659
+ 'cancelOrder',
660
+ [reason]
661
+ );
662
+ }
663
+ }
664
+ ```
665
+
666
+ ## API Reference
667
+
668
+ ### TemporalService
669
+
670
+ The main unified service providing access to all Temporal functionality:
671
+
672
+ ```typescript
673
+ class TemporalService {
674
+ /**
675
+ * Start a workflow execution
676
+ * @param workflowType - Name of the workflow function
677
+ * @param args - Array of arguments to pass to the workflow
678
+ * @param options - Workflow execution options
679
+ */
680
+ async startWorkflow<T>(
681
+ workflowType: string,
682
+ args?: unknown[],
683
+ options?: WorkflowStartOptions
684
+ ): Promise<WorkflowExecutionResult<T>>
685
+
686
+ /**
687
+ * Send a signal to a running workflow
688
+ * @param workflowId - The workflow ID
689
+ * @param signalName - Name of the signal
690
+ * @param args - Arguments for the signal
691
+ */
692
+ async signalWorkflow(
693
+ workflowId: string,
694
+ signalName: string,
695
+ args?: unknown[]
696
+ ): Promise<WorkflowSignalResult>
697
+
698
+ /**
699
+ * Query a running workflow
700
+ * @param workflowId - The workflow ID
701
+ * @param queryName - Name of the query
702
+ * @param args - Arguments for the query
703
+ */
704
+ async queryWorkflow<T>(
705
+ workflowId: string,
706
+ queryName: string,
707
+ args?: unknown[]
708
+ ): Promise<WorkflowQueryResult<T>>
709
+
710
+ /**
711
+ * Get a workflow handle to interact with it
712
+ * @param workflowId - The workflow ID
713
+ * @param runId - Optional run ID for specific execution
714
+ */
715
+ async getWorkflowHandle<T>(
716
+ workflowId: string,
717
+ runId?: string
718
+ ): Promise<T>
719
+
720
+ /**
721
+ * Terminate a workflow execution
722
+ * @param workflowId - The workflow ID
723
+ * @param reason - Termination reason
724
+ */
725
+ async terminateWorkflow(
726
+ workflowId: string,
727
+ reason?: string
728
+ ): Promise<WorkflowTerminationResult>
729
+
730
+ /**
731
+ * Cancel a workflow execution
732
+ * @param workflowId - The workflow ID
733
+ */
734
+ async cancelWorkflow(
735
+ workflowId: string
736
+ ): Promise<WorkflowCancellationResult>
737
+
738
+ /**
739
+ * Get service health status
740
+ */
741
+ getHealth(): ServiceHealth
742
+
743
+ /**
744
+ * Create a schedule
745
+ */
746
+ async createSchedule(options: ScheduleCreateOptions): Promise<ScheduleHandle>
747
+
748
+ /**
749
+ * List all schedules
750
+ */
751
+ async listSchedules(): Promise<ScheduleListDescription[]>
752
+
753
+ /**
754
+ * Delete a schedule
755
+ */
756
+ async deleteSchedule(scheduleId: string): Promise<void>
757
+ }
758
+ ```
759
+
760
+ ### WorkflowStartOptions
761
+
762
+ Options for starting workflows:
763
+
764
+ ```typescript
765
+ interface WorkflowStartOptions {
766
+ workflowId?: string; // Unique workflow ID
767
+ taskQueue?: string; // Task queue name
768
+ workflowExecutionTimeout?: Duration; // Total workflow timeout
769
+ workflowRunTimeout?: Duration; // Single run timeout
770
+ workflowTaskTimeout?: Duration; // Decision task timeout
771
+ memo?: Record<string, unknown>; // Workflow memo
772
+ searchAttributes?: SearchAttributes; // Search attributes for filtering
773
+ }
774
+ ```
775
+
776
+ ### Result Types
777
+
778
+ ```typescript
779
+ interface WorkflowExecutionResult<T> {
780
+ success: boolean;
781
+ result: T; // Contains workflowId, runId, etc.
782
+ executionTime: number;
783
+ error?: Error;
784
+ }
785
+
786
+ interface WorkflowQueryResult<T> {
787
+ success: boolean;
788
+ result: T;
789
+ workflowId: string;
790
+ queryName: string;
791
+ }
792
+
793
+ interface WorkflowSignalResult {
794
+ success: boolean;
795
+ workflowId: string;
796
+ signalName: string;
797
+ }
798
+ ```
799
+
800
+ ## Examples
801
+
802
+ ## Examples
803
+
804
+ ### Example 1: E-commerce Order Processing
805
+
806
+ Complete example with compensation logic:
807
+
808
+ ```typescript
809
+ // order.activity.ts
810
+ @Injectable()
811
+ @Activity({ name: 'order-activities' })
812
+ export class OrderActivity {
813
+ constructor(
814
+ private readonly paymentService: PaymentService,
815
+ private readonly inventoryService: InventoryService,
816
+ private readonly emailService: EmailService,
817
+ ) {}
818
+
819
+ @ActivityMethod('validatePayment')
820
+ async validatePayment(paymentData: PaymentData): Promise<PaymentResult> {
821
+ return await this.paymentService.validate(paymentData);
822
+ }
823
+
824
+ @ActivityMethod('chargePayment')
825
+ async chargePayment(paymentData: PaymentData): Promise<{ transactionId: string }> {
826
+ return await this.paymentService.charge(paymentData);
827
+ }
828
+
829
+ @ActivityMethod('refundPayment')
830
+ async refundPayment(transactionId: string): Promise<void> {
831
+ await this.paymentService.refund(transactionId);
832
+ }
833
+
834
+ @ActivityMethod('reserveInventory')
835
+ async reserveInventory(items: OrderItem[]): Promise<{ reservationId: string }> {
836
+ return await this.inventoryService.reserve(items);
837
+ }
838
+
839
+ @ActivityMethod('releaseInventory')
840
+ async releaseInventory(reservationId: string): Promise<void> {
841
+ await this.inventoryService.release(reservationId);
842
+ }
843
+
844
+ @ActivityMethod('sendConfirmationEmail')
845
+ async sendConfirmationEmail(order: Order): Promise<void> {
846
+ await this.emailService.sendConfirmation(order);
847
+ }
848
+ }
849
+
850
+ // order.workflow.ts
851
+ import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
852
+ import type { OrderActivity } from './order.activity';
853
+
854
+ const {
855
+ validatePayment,
856
+ chargePayment,
857
+ refundPayment,
858
+ reserveInventory,
859
+ releaseInventory,
860
+ sendConfirmationEmail,
861
+ } = proxyActivities<typeof OrderActivity.prototype>({
862
+ startToCloseTimeout: '5m',
863
+ retry: { maximumAttempts: 3 },
864
+ });
865
+
866
+ export const cancelOrderSignal = defineSignal<[string]>('cancelOrder');
867
+ export const getOrderStatusQuery = defineQuery<OrderStatus>('getOrderStatus');
868
+
869
+ export async function processOrderWorkflow(orderData: OrderData): Promise<OrderResult> {
870
+ let status: OrderStatus = 'pending';
871
+ let transactionId: string | undefined;
872
+ let reservationId: string | undefined;
873
+ let cancelled = false;
874
+
875
+ setHandler(cancelOrderSignal, (reason: string) => {
876
+ cancelled = true;
877
+ });
878
+
879
+ setHandler(getOrderStatusQuery, () => status);
880
+
881
+ try {
882
+ // Step 1: Validate payment
883
+ status = 'validating_payment';
884
+ const paymentValid = await validatePayment(orderData.payment);
885
+ if (!paymentValid.valid) {
886
+ throw new Error('Invalid payment method');
803
887
  }
804
- },
805
- taskQueue: 'production-queue',
806
- worker: {
807
- workflowBundle: require('../workflows/bundle'),
808
- workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
888
+
889
+ // Check cancellation
890
+ if (cancelled) {
891
+ status = 'cancelled';
892
+ return { orderId: orderData.orderId, status };
893
+ }
894
+
895
+ // Step 2: Reserve inventory
896
+ status = 'reserving_inventory';
897
+ const reservation = await reserveInventory(orderData.items);
898
+ reservationId = reservation.reservationId;
899
+
900
+ // Step 3: Charge payment
901
+ status = 'charging_payment';
902
+ const payment = await chargePayment(orderData.payment);
903
+ transactionId = payment.transactionId;
904
+
905
+ // Step 4: Send confirmation
906
+ status = 'sending_confirmation';
907
+ await sendConfirmationEmail({
908
+ orderId: orderData.orderId,
909
+ items: orderData.items,
910
+ total: orderData.totalAmount,
911
+ });
912
+
913
+ status = 'completed';
914
+ return {
915
+ orderId: orderData.orderId,
916
+ status,
917
+ transactionId,
918
+ reservationId,
919
+ };
920
+ } catch (error) {
921
+ // Compensation logic
922
+ status = 'compensating';
923
+
924
+ if (reservationId) {
925
+ await releaseInventory(reservationId);
926
+ }
927
+
928
+ if (transactionId) {
929
+ await refundPayment(transactionId);
930
+ }
931
+
932
+ status = 'failed';
933
+ throw error;
809
934
  }
810
- })
935
+ }
936
+
937
+ // order.service.ts
938
+ @Injectable()
939
+ export class OrderService {
940
+ constructor(private readonly temporal: TemporalService) {}
941
+
942
+ async createOrder(orderData: OrderData) {
943
+ const result = await this.temporal.startWorkflow(
944
+ 'processOrderWorkflow',
945
+ [orderData],
946
+ {
947
+ workflowId: `order-${orderData.orderId}`,
948
+ taskQueue: 'order-queue',
949
+ }
950
+ );
951
+
952
+ return result.result;
953
+ }
954
+
955
+ async getOrderStatus(orderId: string) {
956
+ const result = await this.temporal.queryWorkflow(
957
+ `order-${orderId}`,
958
+ 'getOrderStatus'
959
+ );
960
+
961
+ return result.result;
962
+ }
963
+
964
+ async cancelOrder(orderId: string, reason: string) {
965
+ await this.temporal.signalWorkflow(
966
+ `order-${orderId}`,
967
+ 'cancelOrder',
968
+ [reason]
969
+ );
970
+ }
971
+ }
811
972
  ```
812
973
 
813
- ## 📋 Best Practices
974
+ ### Example 2: Scheduled Reports
814
975
 
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
976
+ Creating and managing scheduled workflows:
820
977
 
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
978
+ ```typescript
979
+ // report.activity.ts
980
+ @Injectable()
981
+ @Activity({ name: 'report-activities' })
982
+ export class ReportActivity {
983
+ constructor(
984
+ private readonly reportService: ReportService,
985
+ private readonly storageService: StorageService,
986
+ private readonly notificationService: NotificationService,
987
+ ) {}
988
+
989
+ @ActivityMethod('generateSalesReport')
990
+ async generateSalesReport(period: ReportPeriod): Promise<ReportData> {
991
+ return await this.reportService.generateSales(period);
992
+ }
826
993
 
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
994
+ @ActivityMethod('uploadReport')
995
+ async uploadReport(reportData: ReportData): Promise<string> {
996
+ return await this.storageService.upload(reportData);
997
+ }
832
998
 
833
- ### 4. **Monitoring & Observability**
834
- - Implement health checks
835
- - Monitor worker status
836
- - Track schedule execution
837
- - Use structured logging
999
+ @ActivityMethod('notifyStakeholders')
1000
+ async notifyStakeholders(reportUrl: string, recipients: string[]): Promise<void> {
1001
+ await this.notificationService.send(recipients, reportUrl);
1002
+ }
1003
+ }
838
1004
 
839
- ### 5. **Production Deployment**
840
- - Use pre-bundled workflows
841
- - Configure appropriate worker limits
842
- - Enable TLS for security
843
- - Implement graceful shutdowns
1005
+ // report.workflow.ts
1006
+ import { proxyActivities } from '@temporalio/workflow';
1007
+ import type { ReportActivity } from './report.activity';
844
1008
 
845
- ## 📚 API Reference
1009
+ const { generateSalesReport, uploadReport, notifyStakeholders } =
1010
+ proxyActivities<typeof ReportActivity.prototype>({
1011
+ startToCloseTimeout: '10m',
1012
+ });
846
1013
 
847
- ### Core Decorators
1014
+ export async function weeklyReportWorkflow(): Promise<ReportResult> {
1015
+ const endDate = new Date();
1016
+ const startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
848
1017
 
849
- #### Activity Decorators
850
- - `@Activity(options?)` - Mark a class as containing Temporal activities
851
- - `@ActivityMethod(nameOrOptions?)` - Define an activity method with optional configuration
1018
+ // Generate report
1019
+ const reportData = await generateSalesReport({
1020
+ startDate,
1021
+ endDate,
1022
+ type: 'weekly',
1023
+ });
852
1024
 
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
1025
+ // Upload to storage
1026
+ const reportUrl = await uploadReport(reportData);
857
1027
 
858
- #### Workflow Decorators
859
- - `@Signal(nameOrOptions?)` - Mark a method as a signal handler
860
- - `@Query(nameOrOptions?)` - Mark a method as a query handler
1028
+ // Notify stakeholders
1029
+ await notifyStakeholders(reportUrl, ['management@company.com']);
861
1030
 
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
1031
+ return {
1032
+ reportUrl,
1033
+ generatedAt: new Date(),
1034
+ period: { startDate, endDate },
1035
+ };
1036
+ }
868
1037
 
869
- ### Core Services
1038
+ // schedule.service.ts
1039
+ @Injectable()
1040
+ export class ReportScheduleService {
1041
+ constructor(private readonly temporal: TemporalService) {}
870
1042
 
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
1043
+ async setupWeeklyReports() {
1044
+ await this.temporal.createSchedule({
1045
+ scheduleId: 'weekly-sales-report',
1046
+ spec: {
1047
+ cronExpressions: ['0 9 * * MON'], // Every Monday at 9 AM
1048
+ },
1049
+ action: {
1050
+ type: 'startWorkflow',
1051
+ workflowType: 'weeklyReportWorkflow',
1052
+ taskQueue: 'reports-queue',
1053
+ },
1054
+ policies: {
1055
+ overlap: 'SKIP',
1056
+ catchupWindow: '1 hour',
1057
+ },
1058
+ });
1059
+ }
876
1060
 
877
- ### Utility Functions
1061
+ async deleteSchedule(scheduleId: string) {
1062
+ await this.temporal.deleteSchedule(scheduleId);
1063
+ }
1064
+
1065
+ async listAllSchedules() {
1066
+ return await this.temporal.listSchedules();
1067
+ }
1068
+ }
1069
+ ```
878
1070
 
879
- #### Validation
880
- - `isValidCronExpression(cron: string): boolean` - Validate cron format
881
- - `isValidIntervalExpression(interval: string): boolean` - Validate interval format
1071
+ ## Advanced Usage
882
1072
 
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
1073
+ ### Activity Retry Configuration
889
1074
 
890
- #### Logging
891
- - `TemporalLogger` - Enhanced logger with context support
892
- - `TemporalLoggerManager` - Global logger configuration
1075
+ Configure custom retry policies for different activity types:
893
1076
 
894
- ### Predefined Constants
1077
+ ```typescript
1078
+ // workflow.ts
1079
+ const paymentActivities = proxyActivities<typeof PaymentActivity.prototype>({
1080
+ startToCloseTimeout: '5m',
1081
+ retry: {
1082
+ maximumAttempts: 5,
1083
+ initialInterval: '1s',
1084
+ maximumInterval: '1m',
1085
+ backoffCoefficient: 2,
1086
+ nonRetryableErrorTypes: ['InvalidPaymentMethod', 'InsufficientFunds'],
1087
+ },
1088
+ });
895
1089
 
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.)
1090
+ const emailActivities = proxyActivities<typeof EmailActivity.prototype>({
1091
+ startToCloseTimeout: '2m',
1092
+ retry: {
1093
+ maximumAttempts: 3,
1094
+ initialInterval: '500ms',
1095
+ },
1096
+ });
1097
+ ```
899
1098
 
900
- #### Configuration Presets
901
- - `TIMEOUTS` - Common timeout values for different operation types
902
- - `RETRY_POLICIES` - Predefined retry policies (QUICK, STANDARD, AGGRESSIVE)
1099
+ ### Workflow Testing
903
1100
 
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
1101
+ Test workflows using Temporal's testing framework:
908
1102
 
909
- ## 🤝 Contributing
1103
+ ```typescript
1104
+ import { TestWorkflowEnvironment } from '@temporalio/testing';
1105
+ import { Worker } from '@temporalio/worker';
1106
+ import { processOrderWorkflow } from './order.workflow';
1107
+ import { OrderActivity } from './order.activity';
910
1108
 
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.
1109
+ describe('Order Workflow', () => {
1110
+ let testEnv: TestWorkflowEnvironment;
912
1111
 
913
- ## 📄 License
1112
+ beforeAll(async () => {
1113
+ testEnv = await TestWorkflowEnvironment.createTimeSkipping();
1114
+ });
914
1115
 
915
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1116
+ afterAll(async () => {
1117
+ await testEnv?.teardown();
1118
+ });
916
1119
 
917
- ## 🙏 Acknowledgments
1120
+ it('should process order successfully', async () => {
1121
+ const { client, nativeConnection } = testEnv;
918
1122
 
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
1123
+ // Mock activities
1124
+ const mockOrderActivity = {
1125
+ validatePayment: async () => ({ valid: true }),
1126
+ reserveInventory: async () => ({ reservationId: 'res-123' }),
1127
+ chargePayment: async () => ({ transactionId: 'txn-123' }),
1128
+ sendConfirmationEmail: async () => {},
1129
+ };
922
1130
 
923
- ---
1131
+ const worker = await Worker.create({
1132
+ connection: nativeConnection,
1133
+ taskQueue: 'test',
1134
+ workflowsPath: require.resolve('./order.workflow'),
1135
+ activities: mockOrderActivity,
1136
+ });
1137
+
1138
+ await worker.runUntil(async () => {
1139
+ const result = await client.workflow.execute(processOrderWorkflow, {
1140
+ workflowId: 'test-order-1',
1141
+ taskQueue: 'test',
1142
+ args: [{
1143
+ orderId: 'order-123',
1144
+ payment: { amount: 100, currency: 'USD' },
1145
+ items: [{ id: '1', quantity: 1 }],
1146
+ }],
1147
+ });
1148
+
1149
+ expect(result.status).toBe('completed');
1150
+ expect(result.transactionId).toBe('txn-123');
1151
+ });
1152
+ });
1153
+ });
1154
+ ```
924
1155
 
925
- Built with ❤️ for the NestJS and Temporal communities
1156
+ ### Child Workflows
926
1157
 
927
- ## Workflow Implementation Approaches
1158
+ Organize complex workflows using child workflows:
928
1159
 
929
- NestJS Temporal Core supports two main ways to define workflows:
1160
+ ```typescript
1161
+ // parent.workflow.ts
1162
+ import { startChild } from '@temporalio/workflow';
1163
+
1164
+ export async function parentWorkflow(orderId: string) {
1165
+ // Start child workflows
1166
+ const paymentHandle = await startChild(processPaymentWorkflow, {
1167
+ workflowId: `payment-${orderId}`,
1168
+ args: [paymentData],
1169
+ });
1170
+
1171
+ const shippingHandle = await startChild(processShippingWorkflow, {
1172
+ workflowId: `shipping-${orderId}`,
1173
+ args: [shippingData],
1174
+ });
930
1175
 
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
1176
+ // Wait for both to complete
1177
+ const [paymentResult, shippingResult] = await Promise.all([
1178
+ paymentHandle.result(),
1179
+ shippingHandle.result(),
1180
+ ]);
1181
+
1182
+ return {
1183
+ payment: paymentResult,
1184
+ shipping: shippingResult,
1185
+ };
1186
+ }
1187
+ ```
1188
+
1189
+ ### Continue-As-New for Long-Running Workflows
1190
+
1191
+ Use continue-as-new to prevent event history from growing too large:
939
1192
 
940
1193
  ```typescript
941
- // workflows/email.workflow.ts
942
- export async function processEmailWorkflow(userId: string, emailData: { to: string; subject: string; body: string }) {
943
- // ...
1194
+ import { continueAsNew } from '@temporalio/workflow';
1195
+
1196
+ export async function processEventStreamWorkflow(cursor: number): Promise<void> {
1197
+ const events = await fetchEvents(cursor);
1198
+
1199
+ for (const event of events) {
1200
+ await processEvent(event);
1201
+ }
1202
+
1203
+ // Continue as new after processing 1000 events
1204
+ if (events.length >= 1000) {
1205
+ await continueAsNew<typeof processEventStreamWorkflow>(cursor + events.length);
1206
+ }
944
1207
  }
945
1208
  ```
946
1209
 
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
1210
+ ### Custom Error Handling
1211
+
1212
+ Implement custom error types and handling:
956
1213
 
957
1214
  ```typescript
958
- @Injectable()
959
- export class OrderWorkflowController {
960
- async processOrder(
961
- @WorkflowParam(0) orderId: string,
962
- @WorkflowId() workflowId: string,
963
- @WorkflowContext() context: any
964
- ) {
965
- // ...
1215
+ // activities
1216
+ export class RetryableError extends Error {
1217
+ constructor(message: string) {
1218
+ super(message);
1219
+ this.name = 'RetryableError';
966
1220
  }
967
1221
  }
1222
+
1223
+ export class NonRetryableError extends Error {
1224
+ constructor(message: string) {
1225
+ super(message);
1226
+ this.name = 'NonRetryableError';
1227
+ }
1228
+ }
1229
+
1230
+ @ActivityMethod('processData')
1231
+ async processData(data: any): Promise<any> {
1232
+ try {
1233
+ return await this.externalApi.process(data);
1234
+ } catch (error) {
1235
+ if (error.code === 'RATE_LIMIT') {
1236
+ throw new RetryableError('Rate limit exceeded, will retry');
1237
+ } else if (error.code === 'INVALID_DATA') {
1238
+ throw new NonRetryableError('Invalid data format');
1239
+ }
1240
+ throw error;
1241
+ }
1242
+ }
1243
+
1244
+ // workflow configuration
1245
+ const activities = proxyActivities<typeof DataActivity.prototype>({
1246
+ startToCloseTimeout: '5m',
1247
+ retry: {
1248
+ nonRetryableErrorTypes: ['NonRetryableError'],
1249
+ },
1250
+ });
968
1251
  ```
969
1252
 
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.
1253
+ ## Best Practices
1254
+
1255
+ ### 1. Workflow Design
1256
+
1257
+ **✅ DO:**
1258
+ - Keep workflows deterministic (no random numbers, current time, network calls)
1259
+ - Use activities for any non-deterministic operations
1260
+ - Keep workflow history size manageable (use continue-as-new for long-running workflows)
1261
+ - Export workflow functions (not classes)
1262
+ - Use `defineSignal` and `defineQuery` at module level
1263
+
1264
+ **❌ DON'T:**
1265
+ - Don't use `@Injectable()` on workflow functions
1266
+ - Don't inject NestJS services in workflows
1267
+ - Don't use `Math.random()` or `Date.now()` directly in workflows
1268
+ - Don't make HTTP calls or database queries directly in workflows
1269
+
1270
+ ### 2. Activity Design
1271
+
1272
+ **✅ DO:**
1273
+ - Make activities idempotent (safe to retry)
1274
+ - Use `@Injectable()` and leverage NestJS DI
1275
+ - Use `@Activity()` and `@ActivityMethod()` decorators
1276
+ - Handle errors appropriately
1277
+ - Log activity execution for debugging
1278
+
1279
+ **❌ DON'T:**
1280
+ - Don't make activities too granular (network overhead)
1281
+ - Don't rely on activity execution order guarantees
1282
+ - Don't share mutable state between activity invocations
1283
+
1284
+ ### 3. Configuration
1285
+
1286
+ **✅ DO:**
1287
+ - Use async configuration for environment-based setup
1288
+ - Configure appropriate timeouts for your use case
1289
+ - Set up proper retry policies
1290
+ - Enable graceful shutdown hooks
1291
+ - Use task queues to organize work
1292
+
1293
+ **❌ DON'T:**
1294
+ - Don't hardcode connection strings
1295
+ - Don't use the same task queue for all workflows
1296
+ - Don't ignore timeout configurations
1297
+
1298
+ ### 4. Error Handling
1299
+
1300
+ **✅ DO:**
1301
+ - Implement compensation logic in workflows
1302
+ - Use appropriate retry policies
1303
+ - Log errors with context
1304
+ - Define non-retryable error types
1305
+ - Handle activity failures gracefully
1306
+
1307
+ **❌ DON'T:**
1308
+ - Don't swallow errors silently
1309
+ - Don't retry indefinitely
1310
+ - Don't ignore business-level failures
1311
+
1312
+ ### 5. Testing
1313
+
1314
+ **✅ DO:**
1315
+ - Write unit tests for activities
1316
+ - Use TestWorkflowEnvironment for integration tests
1317
+ - Mock external dependencies
1318
+ - Test failure scenarios
1319
+ - Test signal and query handlers
1320
+
1321
+ **❌ DON'T:**
1322
+ - Don't skip workflow testing
1323
+ - Don't test against production Temporal server
1324
+ - Don't assume workflows are correct without testing
1325
+
1326
+ ## Health Monitoring
1327
+
1328
+ The package includes comprehensive health monitoring capabilities for production deployments.
1329
+
1330
+ ### Using Built-in Health Module
1331
+
1332
+ ```typescript
1333
+ // app.module.ts
1334
+ import { Module } from '@nestjs/common';
1335
+ import { TemporalModule } from 'nestjs-temporal-core';
1336
+ import { TemporalHealthModule } from 'nestjs-temporal-core/health';
1337
+
1338
+ @Module({
1339
+ imports: [
1340
+ TemporalModule.register({
1341
+ connection: { address: 'localhost:7233' },
1342
+ taskQueue: 'my-queue',
1343
+ worker: {
1344
+ workflowsPath: require.resolve('./workflows'),
1345
+ activityClasses: [MyActivity],
1346
+ },
1347
+ }),
1348
+ TemporalHealthModule, // Adds /health/temporal endpoint
1349
+ ],
1350
+ })
1351
+ export class AppModule {}
1352
+ ```
1353
+
1354
+ ### Custom Health Checks
1355
+
1356
+ ```typescript
1357
+ @Controller('health')
1358
+ export class HealthController {
1359
+ constructor(private readonly temporal: TemporalService) {}
1360
+
1361
+ @Get('/status')
1362
+ async getHealthStatus() {
1363
+ const health = this.temporal.getHealth();
1364
+
1365
+ return {
1366
+ status: health.overallHealth,
1367
+ timestamp: new Date(),
1368
+ services: {
1369
+ client: {
1370
+ healthy: health.client.status === 'healthy',
1371
+ connection: health.client.connectionStatus,
1372
+ },
1373
+ worker: {
1374
+ healthy: health.worker.status === 'healthy',
1375
+ state: health.worker.state,
1376
+ activitiesRegistered: health.worker.activitiesCount,
1377
+ },
1378
+ discovery: {
1379
+ healthy: health.discovery.status === 'healthy',
1380
+ activitiesDiscovered: health.discovery.activitiesDiscovered,
1381
+ },
1382
+ },
1383
+ uptime: health.uptime,
1384
+ };
1385
+ }
1386
+
1387
+ @Get('/detailed')
1388
+ async getDetailedHealth() {
1389
+ const health = this.temporal.getHealth();
1390
+ const stats = this.temporal.getStatistics();
1391
+
1392
+ return {
1393
+ health,
1394
+ statistics: stats,
1395
+ performance: {
1396
+ workflowStartLatency: stats.averageWorkflowStartTime,
1397
+ activityExecutionCount: stats.totalActivitiesExecuted,
1398
+ },
1399
+ };
1400
+ }
1401
+ }
1402
+ ```
1403
+
1404
+ ### Health Check Response
1405
+
1406
+ ```typescript
1407
+ interface ServiceHealth {
1408
+ overallHealth: 'healthy' | 'degraded' | 'unhealthy';
1409
+ client: {
1410
+ status: 'healthy' | 'unhealthy';
1411
+ connectionStatus: 'connected' | 'disconnected';
1412
+ };
1413
+ worker: {
1414
+ status: 'healthy' | 'unhealthy';
1415
+ state: 'RUNNING' | 'STOPPED' | 'FAILED';
1416
+ activitiesCount: number;
1417
+ };
1418
+ discovery: {
1419
+ status: 'healthy' | 'unhealthy';
1420
+ activitiesDiscovered: number;
1421
+ };
1422
+ uptime: number;
1423
+ lastChecked: Date;
1424
+ }
1425
+ ```
1426
+ ```
1427
+
1428
+ ## Troubleshooting
1429
+
1430
+ ### Common Issues and Solutions
1431
+
1432
+ #### 1. Connection Errors
1433
+
1434
+ **Problem:** Cannot connect to Temporal server
1435
+
1436
+ ```
1437
+ Error: Failed to connect to localhost:7233
1438
+ ```
1439
+
1440
+ **Solutions:**
1441
+ ```typescript
1442
+ // Check connection configuration
1443
+ const health = temporalService.getHealth();
1444
+ console.log('Connection status:', health.client.connectionStatus);
1445
+
1446
+ // Verify Temporal server is running
1447
+ // docker ps | grep temporal
1448
+
1449
+ // Check connection settings
1450
+ TemporalModule.register({
1451
+ connection: {
1452
+ address: process.env.TEMPORAL_ADDRESS || 'localhost:7233',
1453
+ namespace: 'default',
1454
+ },
1455
+ })
1456
+ ```
1457
+
1458
+ #### 2. Activity Not Found
1459
+
1460
+ **Problem:** Workflow cannot find registered activities
1461
+
1462
+ ```
1463
+ Error: Activity 'myActivity' not found
1464
+ ```
1465
+
1466
+ **Solutions:**
1467
+ ```typescript
1468
+ // 1. Ensure activity is in activityClasses array
1469
+ TemporalModule.register({
1470
+ worker: {
1471
+ activityClasses: [MyActivity], // Must include the activity class
1472
+ },
1473
+ })
1474
+
1475
+ // 2. Verify activity is registered as provider
1476
+ @Module({
1477
+ providers: [MyActivity], // Must be in providers array
1478
+ })
1479
+
1480
+ // 3. Check activity decorator
1481
+ @Activity({ name: 'my-activities' })
1482
+ export class MyActivity {
1483
+ @ActivityMethod('myActivity')
1484
+ async myActivity() { }
1485
+ }
1486
+
1487
+ // 4. Check discovery status
1488
+ const health = temporalService.getHealth();
1489
+ console.log('Activities discovered:', health.discovery.activitiesDiscovered);
1490
+ ```
1491
+
1492
+ #### 3. Workflow Registration Issues
1493
+
1494
+ **Problem:** Workflow not found or not executing
1495
+
1496
+ ```
1497
+ Error: Workflow 'myWorkflow' not found
1498
+ ```
1499
+
1500
+ **Solutions:**
1501
+ ```typescript
1502
+ // 1. Ensure workflowsPath is correct
1503
+ TemporalModule.register({
1504
+ worker: {
1505
+ workflowsPath: require.resolve('./workflows'), // Must resolve to workflows file/directory
1506
+ },
1507
+ })
1508
+
1509
+ // 2. Export workflow function properly
1510
+ // workflows/index.ts
1511
+ export { processOrderWorkflow } from './order.workflow';
1512
+ export { reportWorkflow } from './report.workflow';
1513
+
1514
+ // 3. Use correct workflow name when starting
1515
+ await temporal.startWorkflow(
1516
+ 'processOrderWorkflow', // Must match exported function name
1517
+ [args],
1518
+ options
1519
+ );
1520
+ ```
1521
+
1522
+ #### 4. Timeout Issues
1523
+
1524
+ **Problem:** Activities or workflows timing out
1525
+
1526
+ ```
1527
+ Error: Activity timed out after 10s
1528
+ ```
1529
+
1530
+ **Solutions:**
1531
+ ```typescript
1532
+ // Configure appropriate timeouts
1533
+ const activities = proxyActivities<typeof MyActivity.prototype>({
1534
+ startToCloseTimeout: '10m', // Increase for long-running activities
1535
+ scheduleToCloseTimeout: '15m', // Total time including queuing
1536
+ scheduleToStartTimeout: '5m', // Time waiting in queue
1537
+ });
1538
+
1539
+ // For workflows
1540
+ await temporal.startWorkflow('myWorkflow', [args], {
1541
+ workflowExecutionTimeout: '24h', // Max total execution time
1542
+ workflowRunTimeout: '12h', // Max single run time
1543
+ workflowTaskTimeout: '10s', // Decision task timeout
1544
+ });
1545
+ ```
1546
+
1547
+ #### 5. Worker Not Starting
1548
+
1549
+ **Problem:** Worker fails to start or crashes
1550
+
1551
+ ```
1552
+ Error: Worker failed to start
1553
+ ```
1554
+
1555
+ **Solutions:**
1556
+ ```typescript
1557
+ // 1. Check worker configuration
1558
+ TemporalModule.register({
1559
+ worker: {
1560
+ autoStart: true, // Ensure autoStart is true
1561
+ workflowsPath: require.resolve('./workflows'),
1562
+ activityClasses: [MyActivity],
1563
+ },
1564
+ })
1565
+
1566
+ // 2. Check logs
1567
+ // Enable debug logging
1568
+ TemporalModule.register({
1569
+ logLevel: 'debug',
1570
+ enableLogger: true,
1571
+ })
1572
+
1573
+ // 3. Verify worker health
1574
+ const health = temporalService.getHealth();
1575
+ console.log('Worker status:', health.worker.state);
1576
+
1577
+ // 4. Check for port conflicts or resource issues
1578
+ ```
1579
+
1580
+ #### 6. Signal/Query Not Working
1581
+
1582
+ **Problem:** Signals or queries not being handled
1583
+
1584
+ **Solutions:**
1585
+ ```typescript
1586
+ // 1. Define signals/queries at module level (not inside workflow)
1587
+ export const mySignal = defineSignal<[string]>('mySignal');
1588
+ export const myQuery = defineQuery<string>('myQuery');
1589
+
1590
+ // 2. Set up handlers in workflow
1591
+ export async function myWorkflow() {
1592
+ let value = 'initial';
1593
+
1594
+ setHandler(mySignal, (newValue: string) => {
1595
+ value = newValue;
1596
+ });
1597
+
1598
+ setHandler(myQuery, () => value);
1599
+
1600
+ // ... workflow logic
1601
+ }
1602
+
1603
+ // 3. Use correct names when signaling/querying
1604
+ await temporal.signalWorkflow(workflowId, 'mySignal', ['newValue']);
1605
+ const result = await temporal.queryWorkflow(workflowId, 'myQuery');
1606
+ ```
1607
+
1608
+ ### Debug Mode
1609
+
1610
+ Enable comprehensive debugging:
1611
+
1612
+ ```typescript
1613
+ TemporalModule.register({
1614
+ logLevel: 'debug',
1615
+ enableLogger: true,
1616
+ connection: {
1617
+ address: 'localhost:7233',
1618
+ },
1619
+ worker: {
1620
+ debugMode: true, // If available
1621
+ },
1622
+ })
1623
+
1624
+ // Check detailed health and statistics
1625
+ const health = temporalService.getHealth();
1626
+ const stats = temporalService.getStatistics();
1627
+ console.log('Health:', JSON.stringify(health, null, 2));
1628
+ console.log('Stats:', JSON.stringify(stats, null, 2));
1629
+ ```
1630
+
1631
+ ### Getting Help
1632
+
1633
+ If you're still experiencing issues:
1634
+
1635
+ 1. **Check the logs** - Enable debug logging to see detailed information
1636
+ 2. **Verify configuration** - Double-check all connection and worker settings
1637
+ 3. **Test connectivity** - Ensure Temporal server is accessible
1638
+ 4. **Review health status** - Use `getHealth()` to identify failing components
1639
+ 5. **Check GitHub Issues** - [Search existing issues](https://github.com/harsh-simform/nestjs-temporal-core/issues)
1640
+ 6. **Create an issue** - Provide logs, configuration, and minimal reproduction
1641
+
1642
+ ## Requirements
1643
+
1644
+ - **Node.js**: >= 16.0.0
1645
+ - **NestJS**: >= 9.0.0
1646
+ - **Temporal Server**: >= 1.20.0
1647
+
1648
+ ## Contributing
1649
+
1650
+ We welcome contributions! To contribute:
1651
+
1652
+ 1. Fork the repository
1653
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1654
+ 3. Make your changes
1655
+ 4. Run tests (`npm test`)
1656
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
1657
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
1658
+ 7. Open a Pull Request
1659
+
1660
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
1661
+
1662
+ ### Development Setup
1663
+
1664
+ ```bash
1665
+ # Clone the repository
1666
+ git clone https://github.com/harsh-simform/nestjs-temporal-core.git
1667
+ cd nestjs-temporal-core
1668
+
1669
+ # Install dependencies
1670
+ npm install
1671
+
1672
+ # Run tests
1673
+ npm test
1674
+
1675
+ # Run tests with coverage
1676
+ npm run test:cov
1677
+
1678
+ # Build the package
1679
+ npm run build
1680
+
1681
+ # Generate documentation
1682
+ npm run docs:generate
1683
+ ```
1684
+
1685
+ ## License
1686
+
1687
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1688
+
1689
+ ## Support and Resources
1690
+
1691
+ - 📚 **Documentation**: [Full API Documentation](https://harsh-simform.github.io/nestjs-temporal-core/)
1692
+ - 🐛 **Issues**: [GitHub Issues](https://github.com/harsh-simform/nestjs-temporal-core/issues)
1693
+ - 💬 **Discussions**: [GitHub Discussions](https://github.com/harsh-simform/nestjs-temporal-core/discussions)
1694
+ - 📦 **NPM**: [nestjs-temporal-core](https://www.npmjs.com/package/nestjs-temporal-core)
1695
+ - 🔄 **Changelog**: [Releases](https://github.com/harsh-simform/nestjs-temporal-core/releases)
1696
+ - 📖 **Example Project**: [nestjs-temporal-core-example](https://github.com/harsh-simform/nestjs-temporal-core-example)
1697
+
1698
+ ## Related Projects
1699
+
1700
+ - [Temporal.io](https://temporal.io/) - The underlying workflow orchestration platform
1701
+ - [NestJS](https://nestjs.com/) - Progressive Node.js framework
1702
+ - [@temporalio/sdk](https://www.npmjs.com/package/@temporalio/client) - Official Temporal TypeScript SDK
974
1703
 
975
1704
  ---
1705
+