nestjs-temporal-core 3.0.4 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +509 -157
- package/dist/activity/index.d.ts +2 -0
- package/dist/activity/index.js +19 -0
- package/dist/activity/index.js.map +1 -0
- package/dist/activity/temporal-activity.module.d.ts +11 -0
- package/dist/activity/temporal-activity.module.js +52 -0
- package/dist/activity/temporal-activity.module.js.map +1 -0
- package/dist/activity/temporal-activity.service.d.ts +46 -0
- package/dist/activity/temporal-activity.service.js +195 -0
- package/dist/activity/temporal-activity.service.js.map +1 -0
- package/dist/client/temporal-client.module.d.ts +1 -1
- package/dist/client/temporal-client.module.js +22 -14
- package/dist/client/temporal-client.module.js.map +1 -1
- package/dist/client/temporal-client.service.d.ts +2 -1
- package/dist/client/temporal-client.service.js +12 -4
- package/dist/client/temporal-client.service.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +4 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +53 -6
- package/dist/schedules/index.d.ts +2 -0
- package/dist/schedules/index.js +19 -0
- package/dist/schedules/index.js.map +1 -0
- package/dist/schedules/temporal-schedules.module.d.ts +11 -0
- package/dist/schedules/temporal-schedules.module.js +55 -0
- package/dist/schedules/temporal-schedules.module.js.map +1 -0
- package/dist/schedules/temporal-schedules.service.d.ts +52 -0
- package/dist/schedules/temporal-schedules.service.js +220 -0
- package/dist/schedules/temporal-schedules.service.js.map +1 -0
- package/dist/temporal.service.js +1 -1
- package/dist/temporal.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/conditional-logger.d.ts +15 -0
- package/dist/utils/conditional-logger.js +58 -0
- package/dist/utils/conditional-logger.js.map +1 -0
- package/dist/worker/temporal-worker-manager.service.js +15 -11
- package/dist/worker/temporal-worker-manager.service.js.map +1 -1
- package/dist/worker/temporal-worker.module.js +2 -0
- package/dist/worker/temporal-worker.module.js.map +1 -1
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@ NestJS Temporal Core brings Temporal's durable execution to NestJS with familiar
|
|
|
23
23
|
- **⚙️ Flexible Setup** - Client-only, worker-only, or unified deployments
|
|
24
24
|
- **🏥 Health Monitoring** - Comprehensive status monitoring and health checks
|
|
25
25
|
- **🔧 Production Ready** - TLS, connection management, graceful shutdowns
|
|
26
|
-
- **📊
|
|
26
|
+
- **📊 Modular Architecture** - Individual modules for specific needs
|
|
27
|
+
- **📝 Configurable Logging** - Fine-grained control over log levels and output
|
|
28
|
+
- **🔐 Enterprise Ready** - Temporal Cloud support with TLS and API keys
|
|
27
29
|
|
|
28
30
|
## 📦 Installation
|
|
29
31
|
|
|
@@ -33,7 +35,9 @@ npm install nestjs-temporal-core @temporalio/client @temporalio/worker @temporal
|
|
|
33
35
|
|
|
34
36
|
## 🚀 Quick Start
|
|
35
37
|
|
|
36
|
-
### 1.
|
|
38
|
+
### 1. Complete Integration (Recommended)
|
|
39
|
+
|
|
40
|
+
For applications that need full Temporal functionality:
|
|
37
41
|
|
|
38
42
|
```typescript
|
|
39
43
|
// app.module.ts
|
|
@@ -48,163 +52,385 @@ import { EmailActivities } from './activities/email.activities';
|
|
|
48
52
|
address: 'localhost:7233',
|
|
49
53
|
namespace: 'default',
|
|
50
54
|
},
|
|
51
|
-
taskQueue: '
|
|
55
|
+
taskQueue: 'main-queue',
|
|
52
56
|
worker: {
|
|
53
57
|
workflowsPath: './dist/workflows',
|
|
54
|
-
activityClasses: [EmailActivities],
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
activityClasses: [EmailActivities],
|
|
59
|
+
autoStart: true
|
|
60
|
+
}
|
|
61
|
+
})
|
|
57
62
|
],
|
|
58
|
-
providers: [EmailActivities],
|
|
63
|
+
providers: [EmailActivities], // Auto-discovered
|
|
59
64
|
})
|
|
60
65
|
export class AppModule {}
|
|
61
66
|
```
|
|
62
67
|
|
|
63
|
-
### 2.
|
|
68
|
+
### 2. Define Activities
|
|
64
69
|
|
|
65
70
|
```typescript
|
|
66
71
|
// activities/email.activities.ts
|
|
67
72
|
import { Injectable } from '@nestjs/common';
|
|
68
73
|
import { Activity, ActivityMethod } from 'nestjs-temporal-core';
|
|
69
74
|
|
|
70
|
-
@Injectable()
|
|
71
75
|
@Activity()
|
|
76
|
+
@Injectable()
|
|
72
77
|
export class EmailActivities {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
|
|
79
|
+
@ActivityMethod({
|
|
80
|
+
name: 'sendEmail',
|
|
81
|
+
timeout: '30s',
|
|
82
|
+
maxRetries: 3
|
|
83
|
+
})
|
|
84
|
+
async sendEmail(to: string, subject: string, body: string): Promise<void> {
|
|
85
|
+
console.log(`Sending email to ${to}: ${subject}`);
|
|
86
|
+
// Your email sending logic here
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
@ActivityMethod()
|
|
81
|
-
async sendNotification(
|
|
82
|
-
console.log(`
|
|
89
|
+
@ActivityMethod('sendNotification')
|
|
90
|
+
async sendNotification(userId: string, message: string): Promise<void> {
|
|
91
|
+
console.log(`Notifying user ${userId}: ${message}`);
|
|
83
92
|
// Your notification logic here
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
95
|
```
|
|
87
96
|
|
|
88
|
-
### 3. Create
|
|
97
|
+
### 3. Create Workflows
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// workflows/email.workflow.ts
|
|
101
|
+
import { proxyActivities } from '@temporalio/workflow';
|
|
102
|
+
import type { EmailActivities } from '../activities/email.activities';
|
|
103
|
+
|
|
104
|
+
const { sendEmail, sendNotification } = proxyActivities<EmailActivities>({
|
|
105
|
+
startToCloseTimeout: '1 minute',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export async function processEmailWorkflow(
|
|
109
|
+
userId: string,
|
|
110
|
+
emailData: { to: string; subject: string; body: string }
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
// Send email
|
|
113
|
+
await sendEmail(emailData.to, emailData.subject, emailData.body);
|
|
114
|
+
|
|
115
|
+
// Send notification
|
|
116
|
+
await sendNotification(userId, 'Email sent successfully');
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Schedule Workflows
|
|
89
121
|
|
|
90
122
|
```typescript
|
|
91
123
|
// services/scheduled.service.ts
|
|
92
124
|
import { Injectable } from '@nestjs/common';
|
|
93
|
-
import { Cron, Interval } from 'nestjs-temporal-core';
|
|
125
|
+
import { Scheduled, Cron, Interval, CRON_EXPRESSIONS } from 'nestjs-temporal-core';
|
|
94
126
|
|
|
95
127
|
@Injectable()
|
|
96
128
|
export class ScheduledService {
|
|
97
|
-
|
|
98
|
-
@
|
|
99
|
-
scheduleId: 'daily-
|
|
100
|
-
|
|
129
|
+
|
|
130
|
+
@Scheduled({
|
|
131
|
+
scheduleId: 'daily-report',
|
|
132
|
+
cron: CRON_EXPRESSIONS.DAILY_8AM,
|
|
133
|
+
description: 'Generate daily sales report',
|
|
134
|
+
taskQueue: 'reports'
|
|
101
135
|
})
|
|
102
136
|
async generateDailyReport(): Promise<void> {
|
|
103
|
-
console.log('Generating daily
|
|
104
|
-
//
|
|
137
|
+
console.log('Generating daily report...');
|
|
138
|
+
// Your report generation logic
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Cron(CRON_EXPRESSIONS.WEEKLY_MONDAY_9AM, {
|
|
142
|
+
scheduleId: 'weekly-cleanup'
|
|
143
|
+
})
|
|
144
|
+
async performWeeklyCleanup(): Promise<void> {
|
|
145
|
+
console.log('Performing weekly cleanup...');
|
|
146
|
+
// Your cleanup logic
|
|
105
147
|
}
|
|
106
148
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
scheduleId: 'hourly-cleanup',
|
|
110
|
-
description: 'Hourly cleanup task'
|
|
149
|
+
@Interval('5m', {
|
|
150
|
+
scheduleId: 'health-check'
|
|
111
151
|
})
|
|
112
|
-
async
|
|
113
|
-
console.log('
|
|
114
|
-
//
|
|
152
|
+
async performHealthCheck(): Promise<void> {
|
|
153
|
+
console.log('Performing health check...');
|
|
154
|
+
// Your health check logic
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
157
|
```
|
|
118
158
|
|
|
119
|
-
###
|
|
159
|
+
### 5. Use in Services
|
|
120
160
|
|
|
121
161
|
```typescript
|
|
122
|
-
// services/
|
|
162
|
+
// services/order.service.ts
|
|
123
163
|
import { Injectable } from '@nestjs/common';
|
|
124
164
|
import { TemporalService } from 'nestjs-temporal-core';
|
|
125
165
|
|
|
126
166
|
@Injectable()
|
|
127
|
-
export class
|
|
167
|
+
export class OrderService {
|
|
128
168
|
constructor(private readonly temporal: TemporalService) {}
|
|
129
169
|
|
|
130
|
-
async
|
|
131
|
-
// Start workflow directly with client
|
|
170
|
+
async createOrder(orderData: any) {
|
|
132
171
|
const { workflowId } = await this.temporal.startWorkflow(
|
|
133
|
-
'
|
|
134
|
-
[
|
|
135
|
-
{
|
|
136
|
-
taskQueue: '
|
|
137
|
-
workflowId: `
|
|
172
|
+
'processOrder',
|
|
173
|
+
[orderData],
|
|
174
|
+
{
|
|
175
|
+
taskQueue: 'orders',
|
|
176
|
+
workflowId: `order-${orderData.id}`,
|
|
177
|
+
searchAttributes: {
|
|
178
|
+
'customer-id': orderData.customerId
|
|
179
|
+
}
|
|
138
180
|
}
|
|
139
181
|
);
|
|
140
182
|
|
|
141
|
-
return workflowId;
|
|
183
|
+
return { workflowId };
|
|
142
184
|
}
|
|
143
185
|
|
|
144
|
-
async
|
|
145
|
-
|
|
186
|
+
async cancelOrder(orderId: string) {
|
|
187
|
+
await this.temporal.signalWorkflow(`order-${orderId}`, 'cancel');
|
|
188
|
+
return { cancelled: true };
|
|
146
189
|
}
|
|
147
190
|
|
|
148
|
-
async
|
|
149
|
-
await this.temporal.
|
|
191
|
+
async getOrderStatus(orderId: string) {
|
|
192
|
+
const status = await this.temporal.queryWorkflow(`order-${orderId}`, 'getStatus');
|
|
193
|
+
return status;
|
|
150
194
|
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
151
197
|
|
|
152
|
-
|
|
153
|
-
async pauseDailyReport(): Promise<void> {
|
|
154
|
-
await this.temporal.pauseSchedule('daily-user-report', 'Maintenance mode');
|
|
155
|
-
}
|
|
198
|
+
## 🏗️ Integration Patterns
|
|
156
199
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
200
|
+
### Client-Only Integration
|
|
201
|
+
|
|
202
|
+
For applications that only start workflows (e.g., web APIs):
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { TemporalClientModule } from 'nestjs-temporal-core';
|
|
206
|
+
|
|
207
|
+
@Module({
|
|
208
|
+
imports: [
|
|
209
|
+
TemporalClientModule.forRoot({
|
|
210
|
+
connection: {
|
|
211
|
+
address: 'localhost:7233',
|
|
212
|
+
namespace: 'production'
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
],
|
|
216
|
+
providers: [ApiService],
|
|
217
|
+
})
|
|
218
|
+
export class ClientOnlyModule {}
|
|
161
219
|
```
|
|
162
220
|
|
|
163
|
-
|
|
221
|
+
### Worker-Only Integration
|
|
164
222
|
|
|
165
|
-
|
|
223
|
+
For dedicated worker processes:
|
|
166
224
|
|
|
167
225
|
```typescript
|
|
168
|
-
|
|
226
|
+
import { TemporalWorkerModule, WORKER_PRESETS } from 'nestjs-temporal-core';
|
|
227
|
+
|
|
228
|
+
@Module({
|
|
229
|
+
imports: [
|
|
230
|
+
TemporalWorkerModule.forRoot({
|
|
231
|
+
connection: {
|
|
232
|
+
address: 'localhost:7233',
|
|
233
|
+
namespace: 'production'
|
|
234
|
+
},
|
|
235
|
+
taskQueue: 'worker-queue',
|
|
236
|
+
workflowsPath: './dist/workflows',
|
|
237
|
+
activityClasses: [ProcessingActivities],
|
|
238
|
+
workerOptions: WORKER_PRESETS.PRODUCTION_HIGH_THROUGHPUT
|
|
239
|
+
})
|
|
240
|
+
],
|
|
241
|
+
providers: [ProcessingActivities],
|
|
242
|
+
})
|
|
243
|
+
export class WorkerOnlyModule {}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Modular Integration
|
|
247
|
+
|
|
248
|
+
Using individual modules for specific needs:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import {
|
|
252
|
+
TemporalClientModule,
|
|
253
|
+
TemporalActivityModule,
|
|
254
|
+
TemporalSchedulesModule
|
|
255
|
+
} from 'nestjs-temporal-core';
|
|
256
|
+
|
|
257
|
+
@Module({
|
|
258
|
+
imports: [
|
|
259
|
+
// Client for workflow operations
|
|
260
|
+
TemporalClientModule.forRoot({
|
|
261
|
+
connection: { address: 'localhost:7233' }
|
|
262
|
+
}),
|
|
263
|
+
|
|
264
|
+
// Activities management
|
|
265
|
+
TemporalActivityModule.forRoot({
|
|
266
|
+
activityClasses: [EmailActivities, PaymentActivities]
|
|
267
|
+
}),
|
|
268
|
+
|
|
269
|
+
// Schedule management
|
|
270
|
+
TemporalSchedulesModule.forRoot({
|
|
271
|
+
autoStart: true,
|
|
272
|
+
defaultTimezone: 'UTC'
|
|
273
|
+
}),
|
|
274
|
+
],
|
|
275
|
+
providers: [EmailActivities, PaymentActivities, ScheduledService],
|
|
276
|
+
})
|
|
277
|
+
export class ModularIntegrationModule {}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## ⚙️ Configuration
|
|
281
|
+
|
|
282
|
+
### Async Configuration
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
286
|
+
|
|
287
|
+
@Module({
|
|
288
|
+
imports: [
|
|
289
|
+
ConfigModule.forRoot(),
|
|
290
|
+
TemporalModule.registerAsync({
|
|
291
|
+
imports: [ConfigModule],
|
|
292
|
+
useFactory: async (config: ConfigService) => ({
|
|
293
|
+
connection: {
|
|
294
|
+
address: config.get('TEMPORAL_ADDRESS'),
|
|
295
|
+
namespace: config.get('TEMPORAL_NAMESPACE'),
|
|
296
|
+
tls: config.get('TEMPORAL_TLS_ENABLED') === 'true',
|
|
297
|
+
apiKey: config.get('TEMPORAL_API_KEY'),
|
|
298
|
+
},
|
|
299
|
+
taskQueue: config.get('TEMPORAL_TASK_QUEUE'),
|
|
300
|
+
worker: {
|
|
301
|
+
workflowsPath: config.get('WORKFLOWS_PATH'),
|
|
302
|
+
activityClasses: [EmailActivities, PaymentActivities],
|
|
303
|
+
autoStart: config.get('WORKER_AUTO_START') !== 'false',
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
inject: [ConfigService],
|
|
307
|
+
})
|
|
308
|
+
],
|
|
309
|
+
})
|
|
310
|
+
export class AppModule {}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Environment-Specific Configurations
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Development
|
|
317
|
+
const developmentConfig = {
|
|
169
318
|
connection: {
|
|
170
319
|
address: 'localhost:7233',
|
|
171
|
-
namespace: '
|
|
320
|
+
namespace: 'development'
|
|
172
321
|
},
|
|
173
|
-
taskQueue: '
|
|
322
|
+
taskQueue: 'dev-queue',
|
|
174
323
|
worker: {
|
|
175
324
|
workflowsPath: './dist/workflows',
|
|
176
|
-
|
|
325
|
+
workerOptions: WORKER_PRESETS.DEVELOPMENT
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Production
|
|
330
|
+
const productionConfig = {
|
|
331
|
+
connection: {
|
|
332
|
+
address: process.env.TEMPORAL_ADDRESS!,
|
|
333
|
+
namespace: process.env.TEMPORAL_NAMESPACE!,
|
|
334
|
+
tls: true,
|
|
335
|
+
apiKey: process.env.TEMPORAL_API_KEY
|
|
177
336
|
},
|
|
178
|
-
|
|
337
|
+
taskQueue: process.env.TEMPORAL_TASK_QUEUE!,
|
|
338
|
+
worker: {
|
|
339
|
+
workflowBundle: require('../workflows/bundle'), // Pre-bundled
|
|
340
|
+
workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
|
|
341
|
+
}
|
|
342
|
+
};
|
|
179
343
|
```
|
|
180
344
|
|
|
181
|
-
|
|
345
|
+
## 📝 Logger Configuration
|
|
346
|
+
|
|
347
|
+
Control logging behavior across all Temporal modules with configurable logger settings:
|
|
348
|
+
|
|
349
|
+
### Basic Logger Setup
|
|
182
350
|
|
|
183
351
|
```typescript
|
|
184
|
-
|
|
352
|
+
// Enable/disable logging and set log levels
|
|
353
|
+
TemporalModule.register({
|
|
185
354
|
connection: {
|
|
186
|
-
address: '
|
|
187
|
-
namespace: '
|
|
188
|
-
tls: true,
|
|
355
|
+
address: 'localhost:7233',
|
|
356
|
+
namespace: 'default'
|
|
189
357
|
},
|
|
190
|
-
|
|
358
|
+
taskQueue: 'main-queue',
|
|
359
|
+
// Logger configuration
|
|
360
|
+
enableLogger: true, // Enable/disable all logging
|
|
361
|
+
logLevel: 'info', // Set log level: 'error' | 'warn' | 'info' | 'debug' | 'verbose'
|
|
362
|
+
worker: {
|
|
363
|
+
workflowsPath: './dist/workflows',
|
|
364
|
+
activityClasses: [EmailActivities]
|
|
365
|
+
}
|
|
366
|
+
})
|
|
191
367
|
```
|
|
192
368
|
|
|
193
|
-
###
|
|
369
|
+
### Environment-Based Logger Configuration
|
|
194
370
|
|
|
195
371
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
372
|
+
// Different log levels for different environments
|
|
373
|
+
const loggerConfig = {
|
|
374
|
+
development: {
|
|
375
|
+
enableLogger: true,
|
|
376
|
+
logLevel: 'debug' as const // Show all logs in development
|
|
200
377
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
378
|
+
production: {
|
|
379
|
+
enableLogger: true,
|
|
380
|
+
logLevel: 'warn' as const // Only warnings and errors in production
|
|
381
|
+
},
|
|
382
|
+
testing: {
|
|
383
|
+
enableLogger: false // Disable logging during tests
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
TemporalModule.register({
|
|
388
|
+
connection: { address: 'localhost:7233' },
|
|
389
|
+
taskQueue: 'main-queue',
|
|
390
|
+
...loggerConfig[process.env.NODE_ENV || 'development'],
|
|
391
|
+
worker: {
|
|
392
|
+
workflowsPath: './dist/workflows'
|
|
393
|
+
}
|
|
394
|
+
})
|
|
205
395
|
```
|
|
206
396
|
|
|
207
|
-
###
|
|
397
|
+
### Individual Module Logger Configuration
|
|
398
|
+
|
|
399
|
+
Configure logging for specific modules:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// Activity Module with custom logging
|
|
403
|
+
TemporalActivityModule.forRoot({
|
|
404
|
+
activityClasses: [EmailActivities],
|
|
405
|
+
enableLogger: true,
|
|
406
|
+
logLevel: 'debug'
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// Schedules Module with minimal logging
|
|
410
|
+
TemporalSchedulesModule.forRoot({
|
|
411
|
+
autoStart: true,
|
|
412
|
+
enableLogger: true,
|
|
413
|
+
logLevel: 'error' // Only show errors
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
// Client Module with no logging
|
|
417
|
+
TemporalClientModule.forRoot({
|
|
418
|
+
connection: { address: 'localhost:7233' },
|
|
419
|
+
enableLogger: false
|
|
420
|
+
})
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Log Level Hierarchy
|
|
424
|
+
|
|
425
|
+
The logger follows a hierarchical structure where each level includes all levels above it:
|
|
426
|
+
|
|
427
|
+
- **`error`**: Only critical errors
|
|
428
|
+
- **`warn`**: Errors + warnings
|
|
429
|
+
- **`info`**: Errors + warnings + informational messages (default)
|
|
430
|
+
- **`debug`**: Errors + warnings + info + debug information
|
|
431
|
+
- **`verbose`**: All messages including verbose details
|
|
432
|
+
|
|
433
|
+
### Async Configuration with Logger
|
|
208
434
|
|
|
209
435
|
```typescript
|
|
210
436
|
TemporalModule.registerAsync({
|
|
@@ -212,130 +438,256 @@ TemporalModule.registerAsync({
|
|
|
212
438
|
useFactory: (config: ConfigService) => ({
|
|
213
439
|
connection: {
|
|
214
440
|
address: config.get('TEMPORAL_ADDRESS'),
|
|
215
|
-
namespace: config.get('TEMPORAL_NAMESPACE')
|
|
441
|
+
namespace: config.get('TEMPORAL_NAMESPACE')
|
|
216
442
|
},
|
|
217
443
|
taskQueue: config.get('TEMPORAL_TASK_QUEUE'),
|
|
444
|
+
// Dynamic logger configuration
|
|
445
|
+
enableLogger: config.get('TEMPORAL_LOGGING_ENABLED', 'true') === 'true',
|
|
446
|
+
logLevel: config.get('TEMPORAL_LOG_LEVEL', 'info'),
|
|
218
447
|
worker: {
|
|
219
|
-
workflowsPath: './dist/workflows'
|
|
220
|
-
|
|
221
|
-
},
|
|
448
|
+
workflowsPath: './dist/workflows'
|
|
449
|
+
}
|
|
222
450
|
}),
|
|
223
|
-
inject: [ConfigService]
|
|
224
|
-
})
|
|
451
|
+
inject: [ConfigService]
|
|
452
|
+
})
|
|
225
453
|
```
|
|
226
454
|
|
|
227
|
-
|
|
455
|
+
### Logger Examples
|
|
228
456
|
|
|
229
|
-
### Auto-Discovery
|
|
230
|
-
The module automatically discovers and registers:
|
|
231
|
-
- **Activity Classes** marked with `@Activity`
|
|
232
|
-
- **Scheduled Workflows** marked with `@Cron` or `@Interval`
|
|
233
|
-
- **Signals and Queries** within classes
|
|
234
|
-
|
|
235
|
-
### Scheduling Made Simple
|
|
236
457
|
```typescript
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
458
|
+
// Silent mode - no logs
|
|
459
|
+
{
|
|
460
|
+
enableLogger: false
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Error only - for production monitoring
|
|
464
|
+
{
|
|
465
|
+
enableLogger: true,
|
|
466
|
+
logLevel: 'error'
|
|
241
467
|
}
|
|
242
|
-
```
|
|
243
468
|
|
|
244
|
-
|
|
469
|
+
// Development mode - detailed logging
|
|
470
|
+
{
|
|
471
|
+
enableLogger: true,
|
|
472
|
+
logLevel: 'debug'
|
|
473
|
+
}
|
|
245
474
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
@Cron('0 0 * * 0', { scheduleId: 'weekly-sales-report' })
|
|
251
|
-
async generateWeeklySalesReport(): Promise<void> {
|
|
252
|
-
// Automatically runs every Sunday at midnight
|
|
253
|
-
console.log('Generating weekly sales report...');
|
|
254
|
-
}
|
|
475
|
+
// Verbose mode - maximum detail for troubleshooting
|
|
476
|
+
{
|
|
477
|
+
enableLogger: true,
|
|
478
|
+
logLevel: 'verbose'
|
|
255
479
|
}
|
|
256
480
|
```
|
|
257
481
|
|
|
258
|
-
|
|
482
|
+
## 📊 Health Monitoring
|
|
483
|
+
|
|
484
|
+
Built-in health monitoring for production environments:
|
|
485
|
+
|
|
259
486
|
```typescript
|
|
260
|
-
@
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return
|
|
487
|
+
@Controller('health')
|
|
488
|
+
export class HealthController {
|
|
489
|
+
constructor(private readonly temporal: TemporalService) {}
|
|
490
|
+
|
|
491
|
+
@Get('temporal')
|
|
492
|
+
async getTemporalHealth() {
|
|
493
|
+
const health = await this.temporal.getOverallHealth();
|
|
494
|
+
return {
|
|
495
|
+
status: health.status,
|
|
496
|
+
components: health.components,
|
|
497
|
+
timestamp: new Date().toISOString()
|
|
498
|
+
};
|
|
268
499
|
}
|
|
269
500
|
|
|
270
|
-
@
|
|
271
|
-
async
|
|
272
|
-
|
|
273
|
-
|
|
501
|
+
@Get('temporal/detailed')
|
|
502
|
+
async getDetailedStatus() {
|
|
503
|
+
const systemStatus = await this.temporal.getSystemStatus();
|
|
504
|
+
const stats = this.temporal.getDiscoveryStats();
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
system: systemStatus,
|
|
508
|
+
discovery: stats,
|
|
509
|
+
schedules: this.temporal.getScheduleStats()
|
|
510
|
+
};
|
|
274
511
|
}
|
|
275
512
|
}
|
|
276
513
|
```
|
|
277
514
|
|
|
278
|
-
|
|
515
|
+
## 🔧 Advanced Features
|
|
516
|
+
|
|
517
|
+
### Activity Options
|
|
518
|
+
|
|
279
519
|
```typescript
|
|
520
|
+
@Activity()
|
|
280
521
|
@Injectable()
|
|
281
|
-
export class
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
522
|
+
export class PaymentActivities {
|
|
523
|
+
|
|
524
|
+
@ActivityMethod({
|
|
525
|
+
name: 'processPayment',
|
|
526
|
+
timeout: '2m',
|
|
527
|
+
maxRetries: 5,
|
|
528
|
+
retryPolicy: {
|
|
529
|
+
maximumAttempts: 5,
|
|
530
|
+
initialInterval: '1s',
|
|
531
|
+
maximumInterval: '60s',
|
|
532
|
+
backoffCoefficient: 2.0
|
|
533
|
+
}
|
|
285
534
|
})
|
|
286
|
-
async
|
|
287
|
-
|
|
288
|
-
// Health check logic
|
|
535
|
+
async processPayment(orderId: string, amount: number) {
|
|
536
|
+
// Complex payment processing with retries
|
|
289
537
|
}
|
|
290
538
|
}
|
|
291
539
|
```
|
|
292
540
|
|
|
293
|
-
|
|
541
|
+
### Schedule Management
|
|
294
542
|
|
|
295
543
|
```typescript
|
|
296
544
|
@Injectable()
|
|
297
|
-
export class
|
|
545
|
+
export class ScheduleManagementService {
|
|
298
546
|
constructor(private readonly temporal: TemporalService) {}
|
|
299
547
|
|
|
300
|
-
async
|
|
301
|
-
|
|
302
|
-
const health = await this.temporal.getOverallHealth();
|
|
303
|
-
return {
|
|
304
|
-
status: health.status, // 'healthy' | 'degraded' | 'unhealthy'
|
|
305
|
-
components: health.components,
|
|
306
|
-
};
|
|
548
|
+
async pauseSchedule(scheduleId: string) {
|
|
549
|
+
await this.temporal.pauseSchedule(scheduleId, 'Maintenance mode');
|
|
307
550
|
}
|
|
308
551
|
|
|
309
|
-
async
|
|
310
|
-
|
|
311
|
-
const schedules = this.temporal.getManagedSchedules();
|
|
312
|
-
const stats = this.temporal.getDiscoveryStats();
|
|
313
|
-
|
|
314
|
-
return { schedules, stats };
|
|
552
|
+
async resumeSchedule(scheduleId: string) {
|
|
553
|
+
await this.temporal.resumeSchedule(scheduleId);
|
|
315
554
|
}
|
|
316
555
|
|
|
317
|
-
async
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
556
|
+
async triggerScheduleNow(scheduleId: string) {
|
|
557
|
+
await this.temporal.triggerSchedule(scheduleId);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async getScheduleInfo(scheduleId: string) {
|
|
561
|
+
return this.temporal.getScheduleInfo(scheduleId);
|
|
322
562
|
}
|
|
323
563
|
}
|
|
324
564
|
```
|
|
325
565
|
|
|
326
|
-
|
|
566
|
+
### Workflow Signals and Queries
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// In your workflow
|
|
570
|
+
import { defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
|
|
571
|
+
|
|
572
|
+
export const cancelSignal = defineSignal('cancel');
|
|
573
|
+
export const getStatusQuery = defineQuery<string>('getStatus');
|
|
574
|
+
|
|
575
|
+
export async function orderWorkflow(orderData: any) {
|
|
576
|
+
let status = 'processing';
|
|
577
|
+
let cancelled = false;
|
|
578
|
+
|
|
579
|
+
// Handle cancel signal
|
|
580
|
+
setHandler(cancelSignal, () => {
|
|
581
|
+
cancelled = true;
|
|
582
|
+
status = 'cancelled';
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// Handle status query
|
|
586
|
+
setHandler(getStatusQuery, () => status);
|
|
587
|
+
|
|
588
|
+
// Workflow logic with cancellation support
|
|
589
|
+
if (cancelled) return;
|
|
590
|
+
|
|
591
|
+
// Process order...
|
|
592
|
+
status = 'completed';
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## 🌐 Temporal Cloud Integration
|
|
327
597
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
598
|
+
For Temporal Cloud deployments:
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
TemporalModule.register({
|
|
602
|
+
connection: {
|
|
603
|
+
address: 'your-namespace.account.tmprl.cloud:7233',
|
|
604
|
+
namespace: 'your-namespace.account',
|
|
605
|
+
tls: true,
|
|
606
|
+
apiKey: process.env.TEMPORAL_API_KEY,
|
|
607
|
+
metadata: {
|
|
608
|
+
'temporal-namespace': 'your-namespace.account'
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
taskQueue: 'production-queue',
|
|
612
|
+
worker: {
|
|
613
|
+
workflowBundle: require('../workflows/bundle'),
|
|
614
|
+
workerOptions: WORKER_PRESETS.PRODUCTION_BALANCED
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## 📋 Best Practices
|
|
620
|
+
|
|
621
|
+
### 1. **Activity Design**
|
|
622
|
+
- Keep activities idempotent
|
|
623
|
+
- Use proper timeouts and retry policies
|
|
624
|
+
- Handle errors gracefully
|
|
625
|
+
- Use dependency injection for testability
|
|
626
|
+
|
|
627
|
+
### 2. **Workflow Organization**
|
|
628
|
+
- Separate workflow files from activities
|
|
629
|
+
- Use TypeScript for type safety
|
|
630
|
+
- Keep workflows deterministic
|
|
631
|
+
- Bundle workflows for production
|
|
632
|
+
|
|
633
|
+
### 3. **Configuration Management**
|
|
634
|
+
- Use environment variables for connection settings
|
|
635
|
+
- Separate configs for different environments
|
|
636
|
+
- Use async configuration for dynamic settings
|
|
637
|
+
- Validate configuration at startup
|
|
638
|
+
|
|
639
|
+
### 4. **Monitoring & Observability**
|
|
640
|
+
- Implement health checks
|
|
641
|
+
- Monitor worker status
|
|
642
|
+
- Track schedule execution
|
|
643
|
+
- Use structured logging
|
|
644
|
+
|
|
645
|
+
### 5. **Production Deployment**
|
|
646
|
+
- Use pre-bundled workflows
|
|
647
|
+
- Configure appropriate worker limits
|
|
648
|
+
- Enable TLS for security
|
|
649
|
+
- Implement graceful shutdowns
|
|
650
|
+
|
|
651
|
+
## 📚 API Reference
|
|
652
|
+
|
|
653
|
+
### Decorators
|
|
654
|
+
|
|
655
|
+
- `@Activity()` - Mark a class as containing activities
|
|
656
|
+
- `@ActivityMethod(options?)` - Define an activity method
|
|
657
|
+
- `@Scheduled(options)` - Schedule a workflow with full options
|
|
658
|
+
- `@Cron(expression, options?)` - Schedule using cron expression
|
|
659
|
+
- `@Interval(interval, options?)` - Schedule using interval
|
|
660
|
+
|
|
661
|
+
### Services
|
|
662
|
+
|
|
663
|
+
- `TemporalService` - Main service for all operations
|
|
664
|
+
- `TemporalClientService` - Client-only operations
|
|
665
|
+
- `TemporalActivityService` - Activity management
|
|
666
|
+
- `TemporalSchedulesService` - Schedule management
|
|
667
|
+
- `TemporalWorkerManagerService` - Worker lifecycle
|
|
668
|
+
|
|
669
|
+
### Constants
|
|
670
|
+
|
|
671
|
+
- `CRON_EXPRESSIONS` - Common cron patterns
|
|
672
|
+
- `INTERVAL_EXPRESSIONS` - Common interval patterns
|
|
673
|
+
- `WORKER_PRESETS` - Environment-specific worker configs
|
|
674
|
+
- `RETRY_POLICIES` - Common retry patterns
|
|
675
|
+
- `TIMEOUTS` - Common timeout values
|
|
334
676
|
|
|
335
677
|
## 🤝 Contributing
|
|
336
678
|
|
|
337
|
-
Contributions welcome! Please read our [Contributing Guide](
|
|
679
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
338
680
|
|
|
339
681
|
## 📄 License
|
|
340
682
|
|
|
341
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
683
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
684
|
+
|
|
685
|
+
## 🙏 Acknowledgments
|
|
686
|
+
|
|
687
|
+
- [Temporal.io](https://temporal.io/) for the amazing workflow engine
|
|
688
|
+
- [NestJS](https://nestjs.com/) for the fantastic framework
|
|
689
|
+
- The TypeScript community for excellent tooling
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
Built with ❤️ for the NestJS and Temporal communities
|