nest-temporal-host
v0.0.82
Published
NestJS module for hosting Temporal workers with decorator-driven workflows and activities
Downloads
201
Readme
nest-temporal-host
A NestJS module for hosting Temporal workers with a decorator-driven API. Define workflows and activities using familiar NestJS patterns — no manual handler wiring, full dependency injection in activities, and multi-worker support out of the box.
Installation
npm install nest-temporal-hostSetup
Register TemporalHostModule in your root AppModule. It manages the full worker lifecycle — connecting to Temporal, registering activities and workflows, and shutting down cleanly on process exit.
Synchronous
import { TemporalHostModule } from "nest-temporal-host";
@Module({
imports: [
TemporalHostModule.forRoot({
connection: { address: "localhost:7233" },
namespace: "default",
workers: [
{
taskQueue: "orders",
workflowsPath: require.resolve("./workflows/order.workflow"),
activities: [OrderActivity],
},
],
}),
],
})
export class AppModule {}Asynchronous (with ConfigService)
import { TemporalHostModule } from "nest-temporal-host";
@Module({
imports: [
TemporalHostModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
connection: { address: config.get("TEMPORAL_URL") },
namespace: config.get("TEMPORAL_NAMESPACE"),
workers: [
{
taskQueue: config.get("TASK_QUEUE"),
workflowsPath: require.resolve("./workflows/order.workflow"),
activities: [OrderActivity],
},
],
}),
}),
],
})
export class AppModule {}Multiple workers
A single app can run multiple workers on different task queues concurrently:
TemporalHostModule.forRoot({
connection: { address: "localhost:7233" },
workers: [
{
taskQueue: "payments",
workflowsPath: require.resolve("./workflows/payment.workflow"),
activities: [PaymentActivity],
},
{
taskQueue: "notifications",
workflowsPath: require.resolve("./workflows/notification.workflow"),
activities: [NotificationActivity],
},
],
});Module options
| Option | Type | Default | Description |
| -------------------- | ---------------------------- | ---------------- | ------------------------------------------------ |
| connection.address | string | localhost:7233 | Temporal server gRPC address |
| connection.tls | TLSConfig | — | TLS config for Temporal Cloud or secured servers |
| namespace | string | default | Temporal namespace |
| workers | TemporalWorkerDefinition[] | — | One or more worker definitions |
Worker definition options
| Option | Type | Required | Description |
| --------------- | -------- | -------- | --------------------------------------------------------- |
| taskQueue | string | Yes | Task queue name this worker listens on |
| workflowsPath | string | Yes | Absolute path to the file exporting your workflow classes |
| activities | Type[] | No | @Activity() classes to register with this worker |
Defining Activities
Decorate a class with @Activity(). All public methods are registered as activity implementations. The class participates in NestJS DI — inject any provider as normal.
import { Activity } from "nest-temporal-host";
@Activity()
export class OrderActivity {
constructor(private readonly orderService: OrderService) {}
async processOrder(orderId: string): Promise<string> {
return this.orderService.process(orderId);
}
async sendConfirmation(orderId: string): Promise<void> {
await this.orderService.notify(orderId);
}
}Activities run in the Node.js context — full NestJS DI is available.
Defining Workflows
Extend TemporalWorkflow and decorate the class with @Workflow(). Use method decorators to declare the entry point and handlers — no manual setHandler calls needed.
import {
Workflow,
Execute,
Signal,
Query,
Update,
UpdateValidator,
TemporalWorkflow,
proxyActivities,
} from "nest-temporal-host";
import type { OrderActivity } from "../activities/order.activity";
const { processOrder, sendConfirmation } = proxyActivities<
typeof OrderActivity
>({
startToCloseTimeout: "5 minutes",
});
@Workflow()
export class OrderWorkflow extends TemporalWorkflow {
private status = "pending";
@Execute()
async run(orderId: string): Promise<string> {
await this.waitUntil(() => this.status !== "pending", "1 day");
if (this.status === "approved") {
await processOrder(orderId);
await sendConfirmation(orderId);
}
return this.status;
}
@Signal("approve")
onApprove(by: string): void {
this.status = `approved by ${by}`;
}
@Signal("cancel")
onCancel(reason: string): void {
this.status = `cancelled: ${reason}`;
}
@Query("status")
getStatus(): string {
return this.status;
}
@Update("expedite")
onExpedite(priority: number): string {
this.status = `expedited at priority ${priority}`;
return this.status;
}
@UpdateValidator("expedite")
validateExpedite(priority: number): void {
if (priority < 1 || priority > 5) {
throw new Error("Priority must be between 1 and 5");
}
}
}Workflows run inside Temporal's sandboxed V8 context. NestJS DI is not available here. Use
@Activity()classes for any DI-aware logic.
Decorator Reference
@Activity()
Marks a class as a Temporal activity provider. Implies @Injectable().
@Workflow()
Marks a class as a Temporal workflow. Must extend TemporalWorkflow.
@Execute()
The workflow entry point. Every @Workflow() class must have exactly one @Execute() method. This is the method Temporal invokes when the workflow is started.
@Signal(name: string)
Registers a method as a signal handler. Signals are fire-and-forget — they do not return a value to the caller.
@Query(name: string)
Registers a method as a query handler. Queries are synchronous reads — they must not modify workflow state and return a value immediately.
@Update(name: string)
Registers a method as an update handler. Updates block until the workflow acknowledges the request and can return a value to the caller.
@UpdateValidator(name: string)
Registers a method as a validator for an update. Runs before the @Update handler. Throw an error here to reject the update before any state is mutated.
TemporalWorkflow base class
| Member | Description |
| ------------------------- | --------------------------------------------------------------------------------------------------------- |
| waitUntil(fn, timeout?) | Waits until the predicate returns true or the optional timeout expires. Wraps Temporal's condition(). |
Signal vs Query vs Update
| | Signal | Query | Update |
| ---------------- | ----------------- | ----------------- | ------------------------ |
| Direction | Caller → Workflow | Caller ← Workflow | Caller ↔ Workflow |
| Return value | None | Yes | Yes |
| Blocks caller | No | No | Yes (until acknowledged) |
| Can mutate state | Yes | No | Yes |
| Can be validated | No | — | Yes (@UpdateValidator) |
