@abinnovision/nestjs-hatchet

中文文档
0.9.0发布于 2026/11/10Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 3096

NestJS integration for Hatchet workflow orchestration with declarative task and workflow decorators for building distributed job systems.

原始使用文档
文档目录24 节
  1. Installation
  2. Quick Start
  3. 1. Configure Module
  4. 2. Define a Task
  5. 3. Register and Invoke
  6. Core Concepts
  7. Tasks
  8. Workflows
  9. Events
  10. Advanced Usage
  11. Non-Blocking Execution
  12. Bulk Event Emission
  13. Task interceptors
  14. Client interceptors
  15. Helper Methods
  16. API Reference
  17. Module
  18. Decorators
  19. Host Factories
  20. References
  21. Client
  22. Context Types
  23. Context Properties
  24. License

@abinnovision/nestjs-hatchet

npm

NestJS integration for Hatchet - a distributed workflow orchestration engine. Provides type-safe decorators, services, and event handling for building robust workflows.

Installation

yarn add @abinnovision/nestjs-hatchet @hatchet-dev/typescript-sdk

@hatchet-dev/typescript-sdk is a peer dependency, so your application controls the SDK version. Import SDK types such as Context directly from it.

Quick Start

1. Configure Module

import { HatchetModule } from "@abinnovision/nestjs-hatchet";
import { Module } from "@nestjs/common";

@Module({
  imports: [
    HatchetModule.forRoot({
      config: {
        token: process.env.HATCHET_CLIENT_TOKEN,
        tls_config: { tls_strategy: "none" },
      },
      worker: { name: "my-worker" },
    }),
  ],
})
export class AppModule {}

The worker is stopped through Nest's shutdown hooks, waiting for running tasks to finish. Call app.enableShutdownHooks() so this also happens on SIGTERM/SIGINT. To let the Hatchet SDK handle these signals itself instead, set handleKill: true in the worker options.

2. Define a Task

import { Host, Task, TaskCtx, taskHost } from "@abinnovision/nestjs-hatchet";
import { z } from "zod";

@Host({ name: "process-data" })
export class ProcessDataTask extends taskHost(z.object({ data: z.string() })) {
  @Task({})
  public async task(ctx: TaskCtx<typeof this>) {
    return { result: ctx.input.data.toUpperCase() };
  }
}

3. Register and Invoke

import { Client, HatchetModule, taskRef } from "@abinnovision/nestjs-hatchet";
import { Injectable, Module } from "@nestjs/common";

import { ProcessDataTask } from "./process-data.task";

@Injectable()
export class MyService {
  constructor(private readonly client: Client) {}

  async process(data: string) {
    return this.client.run(taskRef(ProcessDataTask), { data });
  }
}

@Module({
  imports: [HatchetModule.forFeature(taskRef(ProcessDataTask))],
  providers: [ProcessDataTask, MyService],
})
export class ProcessDataModule {}

forFeature() only registers the hosts with the worker. Each host must also be listed in the providers of a module, otherwise the worker cannot resolve it and the application fails to start.

Core Concepts

Tasks

Standalone units of work with a single executable method.

@Host({ name: "cleanup" })
export class CleanupTask extends taskHost() {
  @Task({})
  public async task(ctx: TaskCtx<typeof this>) {
    return { cleaned: true };
  }
}

Workflows

Multi-step processes with task dependencies.

@Host({ name: "order-workflow" })
export class OrderWorkflow extends workflowHost(
  z.object({ orderId: z.string() }),
) {
  @WorkflowTask<typeof OrderWorkflow>({ parents: [] })
  public async validate(ctx: WorkflowCtx<typeof this>) {
    return { valid: true };
  }

  @WorkflowTask<typeof OrderWorkflow>({ parents: ["validate"] })
  public async process(ctx: WorkflowCtx<typeof this>) {
    const validation = await ctx.parent(this.validate);
    return { processed: validation.valid };
  }
}

Events

Type-safe event definitions with schema validation.

// Define event
export const UserCreatedEvent = defineEvent(
  "user:created",
  z.object({ userId: z.string(), email: z.string().email() }),
);

// Workflow triggered by event
@Host({ name: "user-handler", onEvents: ["user:created"] })
export class UserHandlerWorkflow extends workflowHost() {
  @WorkflowTask<typeof UserHandlerWorkflow>({ parents: [] })
  public async handle(ctx: WorkflowCtx<typeof this>) {
    if (UserCreatedEvent.isCtx(ctx)) {
      return { userId: ctx.input.userId };
    }
  }
}

// Emit event
await client.emit(UserCreatedEvent, {
  userId: "123",
  email: "[email protected]",
});

Advanced Usage

Non-Blocking Execution

const runRef = await ctx.run(taskRef(SomeTask), input, { wait: false });
// Continue without waiting...
const result = await runRef.output; // Await when needed

Bulk Event Emission

await client.emit(OrderEvent, [
  { orderId: "1", total: 100 },
  { orderId: "2", total: 200 },
]);

Task interceptors

Task interceptors wrap every task execution on the worker, including each task of a workflow, e.g. for logging or tracing. Pass them to forRoot()/forRootAsync() and list them as providers, since they are resolved from the application rather than instantiated by the module. For producer-side calls, see Client interceptors.

import {
  BaseCtx,
  HatchetModule,
  TaskInterceptor,
} from "@abinnovision/nestjs-hatchet";
import { Injectable, Logger, Module } from "@nestjs/common";

@Injectable()
export class LoggingInterceptor extends TaskInterceptor {
  private readonly logger = new Logger(LoggingInterceptor.name);

  public async intercept<T>(ctx: BaseCtx<any>, next: () => Promise<T>) {
    this.logger.log(`Running ${ctx.fromSDK.taskName()}`);
    return next();
  }
}

@Module({
  imports: [
    HatchetModule.forRoot({
      config: {},
      worker: { name: "my-worker" },
      taskInterceptors: [LoggingInterceptor],
    }),
  ],
  providers: [LoggingInterceptor],
})
export class AppModule {}

Client interceptors

Client interceptors wrap producer-side calls on Client: client.emit (and the deprecated emitBulk) and client.run. Extend ClientInterceptor and implement interceptEmit, interceptRun, or both. Register the class with the clientInterceptors option of forRoot()/forRootAsync() and list it in providers.

The example below propagates a correlation id. The client interceptor writes it into additionalMetadata, and a task interceptor on the worker restores it from the metadata of the running task.

import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";

import {
  BaseCtx,
  ClientInterceptor,
  EmitCtx,
  EmitOptions,
  HatchetModule,
  TaskInterceptor,
  RunCtx,
  RunOptions,
} from "@abinnovision/nestjs-hatchet";
import { Injectable, Module } from "@nestjs/common";

export const correlation = new AsyncLocalStorage<{ id: string }>();

@Injectable()
export class CorrelationClientInterceptor extends ClientInterceptor {
  public override async interceptEmit(
    ctx: EmitCtx,
    next: (options?: EmitOptions) => Promise<unknown>,
  ) {
    const id = correlation.getStore()?.id;
    if (!id) return next();

    return next({
      ...ctx.options,
      additionalMetadata: {
        correlationId: id,
        ...ctx.options?.additionalMetadata,
      },
    });
  }

  public override async interceptRun(
    ctx: RunCtx,
    next: (options?: RunOptions) => Promise<unknown>,
  ) {
    const id = correlation.getStore()?.id;
    if (!id) return next();

    return next({
      ...ctx.options,
      additionalMetadata: {
        correlationId: id,
        ...ctx.options?.additionalMetadata,
      },
    });
  }
}

@Injectable()
export class CorrelationInterceptor extends TaskInterceptor {
  public async intercept<T>(ctx: BaseCtx<any>, next: () => Promise<T>) {
    const id = ctx.fromSDK.additionalMetadata()?.correlationId ?? randomUUID();
    return correlation.run({ id }, next);
  }
}

@Module({
  imports: [
    HatchetModule.forRootAsync({
      useFactory: () => ({ config: {}, worker: { name: "my-worker" } }),
      clientInterceptors: [CorrelationClientInterceptor],
      taskInterceptors: [CorrelationInterceptor],
    }),
  ],
  providers: [CorrelationClientInterceptor, CorrelationInterceptor],
})
export class AppModule {}

Behavior:

  • Each interceptor runs once per call. A bulk emit (array payload) and a run with array input each count as one call.
  • Interceptors change options only. The payload or input is not modifiable.
  • next() keeps the current options. next(options) replaces them for the following interceptors and the SDK call, so spread ctx.options to keep caller values.
  • next(undefined) behaves like next(). To drop all options, pass {}.
  • wait and the return shape of client.run cannot be overridden. RunOptions excludes wait.
  • With wait: true (the default), interceptRun wraps the full run, including awaiting the output. With wait: false it wraps only the enqueue.
  • childKey has no effect on Client.run.
  • Interceptors run in array order, the first being the outermost. An interceptor may skip next() to short-circuit the call.
  • Client interceptor classes must be listed in providers. A missing provider fails application bootstrap.
  • Child runs started with ctx.run inherit the parent's additionalMetadata on Hatchet engine >= v0.104.0, so they need no client interceptor.

Caveats:

  • Calling client.emit or client.run inside an interceptor re-enters the chain. Guard against the recursion yourself.
  • Request-scoped interceptor providers are not supported, same as task interceptors.

Helper Methods

@Host({ name: "my-task" })
export class MyTask extends taskHost(schema) {
  @Task({})
  public async task(ctx: TaskCtx<typeof this>) {
    return this.helper(ctx);
  }

  private async helper(ctx: HelperCtx<typeof this>) {
    return { result: ctx.input.value };
  }
}

API Reference

Module

MethodDescription
HatchetModule.forRoot(config)Initialize with synchronous config
HatchetModule.forRootAsync(options)Initialize with async config factory
HatchetModule.forFeature(...refs)Register tasks/workflows in feature modules

Module options (extras to forRoot/forRootAsync):

OptionDescription
taskInterceptorsTaskInterceptor classes wrapping each task execution
interceptorsDeprecated, use taskInterceptors
clientInterceptorsClientInterceptor classes wrapping emit and run

Decorators

DecoratorDescription
@Host(opts)Mark class as task or workflow host
@Task(opts)Mark method as standalone task
@WorkflowTask(opts)Mark method as workflow step

Host Factories

FactoryDescription
taskHost(schema?)Create TaskHost base class
workflowHost(schema?)Create WorkflowHost base class

References

FunctionDescription
taskRef(TaskClass)Create type-safe task reference
workflowRef(WorkflowClass)Create type-safe workflow reference

Client

MethodDescription
client.run(ref, input, opts?)Execute task/workflow
client.emit(event, payload | payloads)Emit one or many events
client.emitBulk(event, payloads)Emit multiple events (deprecated — use emit)

Calls to emit, emitBulk and run pass through registered client interceptors.

Context Types

TypeDescription
TaskCtx<T>Context for task execution
WorkflowCtx<T>Context for workflow tasks
HelperCtx<T>Context for helper methods

Context Properties

ctx.input       // Validated input data
ctx.fromSDK     // Underlying Hatchet SDK context
ctx.run(ref, input, opts ?)  // Invoke other tasks/workflows
ctx.parent(method)          // Get parent task output (WorkflowCtx only)

License

Apache-2.0

版本与发布记录

@abinnovision/nestjs-hatchet 共发布 44 个版本,首次发布于 2025/12/15;过去 12 个月发布了 44 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.9.0latest2026/11/10今天
0.9.0-beta.21beta2026/11/10今天
0.9.0-beta.202026/11/10今天
0.9.0-beta.192026/11/10今天
0.9.0-beta.152026/11/082 天前
0.9.0-beta.142026/11/082 天前

关于 @abinnovision/nestjs-hatchet 的常见问题

@abinnovision/nestjs-hatchet 是什么?

NestJS integration for Hatchet workflow orchestration with declarative task and workflow decorators for building distributed job systems.

如何安装 @abinnovision/nestjs-hatchet?

在项目目录运行 npm install @abinnovision/nestjs-hatchet,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@nestjs/core(^11.0.0 || ^12.0.0)、@nestjs/common(^11.0.0 || ^12.0.0)、@hatchet-dev/typescript-sdk(^1.45.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@abinnovision/nestjs-hatchet 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@abinnovision/nestjs-hatchet 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@abinnovision/nestjs-hatchet 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@abinnovision/nestjs-hatchet 可以商用吗?

@abinnovision/nestjs-hatchet 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@abinnovision/nestjs-hatchet 的下载量有多少?

根据 npm 官方下载统计,@abinnovision/nestjs-hatchet 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,096 次,日均约 103 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@abinnovision/nestjs-hatchet 还在维护吗?

最新版本 0.9.0 发布于 2026/11/10(今天);至今共发布 44 个版本,其中过去 12 个月发布了 44 个;首次发布于 2025/12/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。