nestjs-temporal

repository·main·Indexed 41 days ago

https://github.com/kurtzl/nestjs-temporal

A NestJS module for seamless integration with the Temporal TypeScript SDK. It allows developers to manage Temporal Workers and Clients within the NestJS dependency injection ecosystem using decorators like @Activities(), @Activity(), and @InjectTemporalClient(). Supports Node.js >= 12.0.0 and NestJS versions 8, 9, 10, and 11. Version 2.1.0.

Tokens
8.7K
Snippets
22
Records
31
Agent score
50%

What's inside nestjs-temporal

  1. How to register multiple workers

    main

    You can register multiple workers by making multiple calls to TemporalModule.registerWorker or TemporalModule.registerWorkerAsync.

    Important: When using multiple workers, you must explicitly specify your activity classes using the activityClasses option. If you do not, the workers may attempt to register all activities marked with @Activity() in the application.

    To avoid conflicts or unintended registrations, it is recommended to import dynamic worker modules into the specific modules containing their respective workflows and activities, rather than registering everything in the root AppModule.

    // Register the client once at the app level
    @Module({
      imports: [
        TemporalModule.registerClient(),
      ],
    })
    export class AppModule {}
    
    // Configure Worker #1
    @Module({
      imports: [
        TemporalModule.registerWorker({
          workerOptions: {
            taskQueue: 'worker-1',
            workflowsPath: require.resolve('./temporal/workflow-1'),
          },
          activityClasses: [Greeting1Activity],
        }),
      ],
      providers: [Greeting1Activity],
    })
    export class Worker1Module {}
    
    // Configure Worker #2
    @Module({
      imports: [
        TemporalModule.registerWorker({
          workerOptions: {
            taskQueue: 'worker-2',
            workflowsPath: require.resolve('./temporal/workflow-2'),
          },
          activityClasses: [SomeOtherActivity],
        }),
      ],
      providers: [SomeOtherActivity],
    })
    export class Worker2Module {}
  2. Configure Worker and Client asynchronously

    main

    For dynamic configuration (e.g., using ConfigService), use registerWorkerAsync and registerClientAsync.

    When configuring a Worker asynchronously, you can manually establish a NativeConnection, install the Temporal Runtime, and provide a workflowBundle created via bundleWorkflowCode.

    When configuring a Client asynchronously, you can provide a custom connection created via @temporalio/client's Connection.connect method.

    // Async Worker Configuration
    @Module({
      imports: [
        TemporalModule.registerWorkerAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: async (config: ConfigService) => {
            Runtime.install({});
            const temporalHost = config.get('app.temporalHost');
            const connection = await NativeConnection.connect({
              address: temporalHost,
            });
            const workflowBundle = await bundleWorkflowCode({
              workflowsPath: path.join(__dirname, './workflows'),
            });
    
            return {
              workerOptions: {
                connection,
                taskQueue: 'default',
                workflowBundle,
              }
            };
          },
        }),
        ClientModule,
      ],
    })
    export class AppModule {}
    
    // Async Client Configuration
    @Module({
      imports: [
        TemporalModule.registerClientAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: async (config: ConfigService) => {
            const temporalHost = config.get('app.temporalHost');
            const connection = await Connection.connect({
              address: temporalHost,
            });
    
            return {
              connection,
            };
          },
        }),
      ],
    })
    export class ClientModule {}
  3. Quick start with nestjs-temporal

    main

    To get started, register a Worker and a Client in your NestJS application.

    1. Register the Worker: Use TemporalModule.registerWorker to define the taskQueue and the path to your workflow code.
    2. Register the Client: Use TemporalModule.registerClient() to enable workflow orchestration.
    3. Define Activities: Use the @Activities() decorator on a class and the @Activity() decorator on specific methods.
    4. Invoke Workflows: Inject the WorkflowClient using @InjectTemporalClient() in your controllers or services to start workflows.
    import { Module } from '@nestjs/common';
    import { TemporalModule } from 'nestjs-temporal';
    
    @Module({
      imports: [
        TemporalModule.registerWorker({
          workerOptions: {
            taskQueue: 'default',
            workflowsPath: require.resolve('./temporal/workflow'),
          },
        }),
        TemporalModule.registerClient(),
      ],
    })
    export class AppModule {}
    
    // --- Activity Definition ---
    
    import { Injectable } from '@nestjs/common';
    import { Activities, Activity } from 'nestjs-temporal';
    
    @Injectable()
    @Activities()
    export class GreetingActivity {
      @Activity()
      async greeting(name: string): Promise<string> {
        return 'Hello ' + name;
      }
    }
    
    // --- Workflow Usage (Temporal SDK side) ---
    
    import { proxyActivities } from '@temporalio/workflow';
    import { IGreetingActivity } from '../activities';
    
    const { greeting } = proxyActivities<IGreetingActivity>({
      startToCloseTimeout: '1 minute',
    });
    
    export async function example(name: string): Promise<string> {
      return await greeting(name);
    }
    
    // --- Controller Usage ---
    
    import { Controller, Post } from '@nestjs/common';
    import { WorkflowClient } from '@temporalio/client';
    import { InjectTemporalClient } from 'nestjs-temporal';
    
    @Controller()
    export class AppController {
      constructor(
        @InjectTemporalClient() private readonly temporalClient: WorkflowClient,
      ) {}
    
      @Post()
      async greeting() {
        const handle = await this.temporalClient.start('example', {
          args: ['Temporal'],
          taskQueue: 'default',
          workflowId: 'wf-id-' + Math.floor(Math.random() * 1000),
        });
        console.log(`Started workflow ${handle.workflowId}`);
      }
    }
  4. Configure shared Temporal connection settings

    main

    When setting up nestjs-temporal, you can provide connection settings using the SharedConnectionAsyncConfiguration interface. This allows you to define how the Temporal connection (of type NativeConnectionOptions) is instantiated, supporting standard NestJS provider patterns like useClass, useFactory, or useValue.

    Common ways to provide the configuration include:

    • useValue: Provide a static NativeConnectionOptions object.
    • useClass: Provide a class that implements SharedConnectionConfigurationFactory.
    • useFactory: Provide a function that returns NativeConnectionOptions or a Promise<NativeConnectionOptions>.
    • useExisting: Reuse an existing provider that implements SharedConnectionConfigurationFactory.
    // Example using useFactory
    { 
      useFactory: () => ({
        address: 'localhost:7233',
        // other NativeConnectionOptions from @temporalio/worker
      }),
      inject: [SomeOtherService]
    }
    
    // Example using useClass
    class MyConnectionFactory implements SharedConnectionConfigurationFactory {
      async createSharedConfiguration(): Promise<NativeConnectionOptions> {
        return { address: 'localhost:7233' };
      }
    }
    // Then use: { useClass: MyConnectionFactory }
  5. Configure Shared Worker settings using SharedWorkerAsyncConfiguration

    main

    When setting up a shared Temporal worker in NestJS, use the SharedWorkerAsyncConfiguration interface to define how the worker's WorkerOptions (from @temporalio/worker) are provided. This follows standard NestJS dynamic module patterns. You can provide configuration via:

    • useExisting: Reference an existing provider that implements SharedWorkerConfigurationFactory.
    • useClass: Provide a class that implements SharedWorkerConfigurationFactory.
    • useFactory: A factory function that returns WorkerOptions or a Promise<WorkerOptions>. You can use the inject array to provide dependencies to this factory.
    • useValue: Provide a static WorkerOptions object directly.

    Note that if you use useClass or useExisting, the provider must implement the SharedWorkerConfigurationFactory interface.

    // Example using useFactory
    const workerConfig: SharedWorkerAsyncConfiguration = {
      useFactory: (someService: MyService) => {
        return {
          // ... WorkerOptions from @temporalio/worker
          taskQueue: 'my-queue',
        };
      },
      inject: [MyService],
    };
    
    // Example using useValue
    const staticConfig: SharedWorkerAsyncConfiguration = {
      useValue: {
        taskQueue: 'my-queue',
      },
    };
  6. Use Temporal decorators in NestJS

    main

    The nestjs-temporal package provides several decorators to integrate Temporal Workflows and Activities into the NestJS dependency injection system. These decorators allow you to mark classes or methods as Temporal entities and inject the Temporal Client into your services.

    Available decorators include:

    • WorkflowsDecorator: For defining workflow collections.
    • WorkflowDecorator: For marking specific methods as workflows.
    • ActivitiesDecorator: For defining activity collections.
    • ActivityDecorator: For marking specific methods as activities.
    • InjectTemporalClientDecorator: For injecting the TemporalClient into a class constructor.
  7. Create NestJS providers for multiple WorkflowClients with createClientProviders()

    main

    Use createClientProviders to generate an array of NestJS Provider objects. This is useful when you need to support multiple Temporal connections or different workflow configurations within the same NestJS application.

    Each object in the input options array results in a separate provider. The provider is identified by a token generated via getQueueToken(option?.name), allowing you to inject specific clients using their unique name.

    import { createClientProviders } from '@nestjs/temporal';
    import { TemporalModuleOptions } from './interfaces';
    
    const clientOptions: TemporalModuleOptions[] = [
      {
        name: 'primary',
        connection: { address: 'localhost:7233' },
        workflowOptions: { taskQueue: 'primary-queue' },
      },
      {
        name: 'secondary',
        connection: { address: 'other-host:7233' },
        workflowOptions: { taskQueue: 'secondary-queue' },
      },
    ];
    
    const providers = createClientProviders(clientOptions);
    // These providers can then be used in a NestJS Module's 'providers' array
  8. Register a Temporal worker

    main

    To register a Temporal worker that executes activities, use TemporalModule.registerWorker() for synchronous configuration or TemporalModule.registerWorkerAsync() if your configuration depends on other asynchronous providers (like a ConfigService).

    When a worker is registered, the module automatically discovers and registers all activities decorated with @Activities() and @Activity() within the NestJS application context.

    // Synchronous registration
    TemporalModule.registerWorker({
      // Worker configuration options
    })
    
    // Asynchronous registration
    TemporalModule.registerWorkerAsync({
      useFactory: async (configService: ConfigService) => ({
        // Worker configuration options using async data
      }),
      inject: [ConfigService],
    })
  9. Register a method as a Temporal activity using @Activity()

    main

    Use the @Activity() decorator to mark a class method as a Temporal activity. For the decorator to function correctly, the method must reside within a class that has been decorated with @Activities().

    By default, the activity name will be the name of the method. You can override this by providing a string or an ActivityOptions object.

    // Using default method name as activity name
    @Activity()
    async processOrder(orderId: string) { }
    
    // Using a custom string name
    @Activity('custom-activity-name')
    async anotherActivity() { }
    
    // Using an options object
    @Activity({ name: 'complex-activity-name' })
    async thirdActivity() { }
  10. Build a Temporal WorkflowClient with buildClient()

    main

    Use buildClient to manually construct a Temporal WorkflowClient instance. If option.connection is provided, the function will establish a connection using @temporalio/client's Connection.connect before returning the client. If no connection options are provided, it returns a client using the provided workflowOptions.

    import { buildClient } from '@nestjs/temporal';
    import { TemporalModuleOptions } from './interfaces';
    
    const options: TemporalModuleOptions = {
      connection: {
        address: 'localhost:7233',
      },
      workflowOptions: {
        // workflow options here
      },
    };
    
    const client = await buildClient(options);
  11. Import the nestjs-temporal module

    main

    The nestjs-temporal package provides a set of modules, decorators, and explorers to integrate Temporal with NestJS. You can import all public members from the main entrypoint:

    import * as Temporal from 'nestjs-temporal';

    Key components available through this entrypoint include:

    • TemporalModule: The core NestJS module for configuring the Temporal client and workers.
    • TemporalExplorer: Tools for discovering and managing Temporal workflows and activities within the NestJS lifecycle.
    • Decorators: Specialized decorators (e.g., for defining activities or workflows) to integrate Temporal logic into NestJS providers.
    import * as Temporal from 'nestjs-temporal';