Back to list
@hazeljs/flow:Node.jsの高度な実行グラフエンジン
@hazeljs/flow: A Durable Execution Graph Engine for Node.js
Translated: 2026/3/7 11:08:11
Japanese Translation
リンク:
npm @hazeljs/flow
npm @hazeljs/flow-runtime
GitHub (monorepo)
例プロジェクト (@hazeljs-flow-example)
HazelJS 現代のアプリケーションは、サービス間でプロセスを跨いだワークフローや、人による介入が必要となるものだけでなく、実行後に再開する必要があるものも依存します。それは、注文完了、詐欺レビュー、文書承認、またはAIアゲントの統合といった例です。
これらのプロセスは一般的なリクエストレスポンスとは異なり、状態情報を持つ長期的な仕様であるためにステークフォルムやアシスタントなど、手動アップロードが難しいからです。
最も注ぎにNode.jsフレームワークは各リクエストを状態無しと扱います。再開が必要となるたびにプロセス間のフローを「一時休止」としたり、単発の承認や再実行処理を手作業で行っておりましょう。その結果、脆弱なコードが誕生し、ストアドデータの一貫性問題による失敗やダブリング出力などのデバッグ困難な状況に陥ります。
@hazeljs/flow はNode.js開発者に対して提供されるワークフローオペレーターを創造しました:一貫する持久的、審査可能であり復元可能な実行グラフエンジンでハードな部分を処理します。その上記は、保存とポリシー、タイムアウト、イデモトンキィのためなどです。
機能の詳細
@hazeljs/flow エンジンは一時的インメモリストアに自動的に設定されます:全く配置を行わず、DATABASE_URL、無需マイグレーションを提供します。それは開発の地味な環境やテストとデモンストレーションから始めるため理想的です。
バージョンと再開を使用する際には @prisma/client およびから@hazeljs/flow/prisma でプリマスエイダを導入できます。流のスキーママイクレーションを進行とパーステイトとポストグリスアドミッションロックを使います。
再開と手動アップロード プロセス中にあるフローが停止した場合 (ウェブチャネル、単発の承認や、自動化のローティン) フローコアは後を継いで再開に移します。
イデモトンキとダブリング対応 @hazeljs/flow のワット状態にはawaiting_approval エラーにより待機とポーリングの必要性がなくなります。待機するフローはリサブランで継続します。
エイマージョンと再実行 適切なクレジットカード決済、通知送信、またはインベントリ更新 -これらのための一回だけでない処理を防止するために、@hazeljs/flow は各ノードでイデモトンキを使用します。
再実行と補間処理 失敗のスパイク(ネットワークフロー、制限)は頻繁に起こります。 @ hazeljs / flow ノードに対してリトライポリシーを添付できるためです:max_attemptsの回数でリトライ、backoffのタイプ、base_delay_msとmax_delay_msを設定すれば処理となります。
タイムスタンプ エージが永続的なタイムアウトを超える場合、エンジンはノードにタイムアウトエラーをスプートし (再実行可) リトライポリシーがある限りです。
ブランチと条件 ノード間の分岐もサポートします : risk_ score < 30の場合、「承認」、risk_ score < 70の場合、「チェック」を引続きする仕様を指定します。 @hazeljs/flow のエッジでポリシーは優先順位に適用されても一度限りと予期しない状況が起きます @hazeljs/flow ではエラー AMBIGUOUS_EDGEをスプートし、一貫した挙動を見たいからです。
複数のプロセストラック中に実行可能なノードは存在することもわかります。一貫するエディターロックでこれを防ぎます:複数のフローコアが同時に処理できないため。@hazeljs/flow ではこれはPostgresにリクエストを出すためです。
Original Content
Links: npm @hazeljs/flow · npm @hazeljs/flow-runtime · GitHub (monorepo) · Example repo (hazeljs-flow-example) · HazelJS Modern applications increasingly rely on workflows—multi-step processes that span services, require human input, and must survive restarts. Think order fulfillment, fraud review, document approval, onboarding sequences, or AI agent orchestration. These aren't simple request-response; they're stateful, long-running, and often asynchronous. The problem: most Node.js frameworks treat each request as stateless. When you need to "pause" a workflow and resume it later—after a webhook, a manual approval, or a retry—you're on your own. You end up hand-rolling state machines, polling loops, and ad-hoc persistence. That leads to brittle code, lost state, duplicate charges, and debugging nightmares. @hazeljs/flow was created to give Node.js developers a workflow OS kernel: a durable, auditable, resumable execution graph engine that handles the hard parts—persistence, retries, timeouts, idempotency, and concurrency—so you can focus on business logic. You can run without any database. The engine uses in-memory storage by default: zero config, no DATABASE_URL, no migrations. Ideal for local development, tests, demos, or lightweight deployments. When you need durable persistence (crash recovery, multi-process, audit in Postgres), install @prisma/client and use the Prisma adapter from @hazeljs/flow/prisma: pass storage: createPrismaStorage(prisma) into FlowEngine. Run the flow schema migrations and you get the same API with full persistence and Postgres advisory locks. Durability and Crash Recovery (with Prisma storage) When using the optional Prisma storage, every run's state is persisted to Postgres. If your process crashes mid-flow, the runtime can pick up RUNNING flows on restart and continue them. No manual recovery scripts, no "start from scratch" UX. With in-memory storage, runs survive only for the lifetime of the process—use it when that's acceptable. Wait-and-Resume (Human-in-the-Loop) Many workflows need to pause for external input: a manager's approval, a payment confirmation, a customer response. Most systems force you to poll, use webhooks with custom state lookup, or build a queue. @hazeljs/flow has a first-class WAIT state. A node returns { status: 'wait', reason: 'awaiting_approval' }, and the run is persisted (or held in memory). When the approval arrives, you call resumeRun(runId, payload) and execution continues. No polling, no custom state tables. Idempotency and Duplicate Prevention Charging a card, sending a notification, or updating inventory—these must not run twice. @hazeljs/flow supports idempotency keys per node. If a node has already run with the same key (e.g. order:ORD-123:charge), the engine reuses the cached output instead of re-executing. Critical for payments and external APIs. Retries and Backoff Transient failures (network blips, rate limits) are common. @hazeljs/flow lets you attach a retry policy to any node: maxAttempts, backoff: 'fixed' | 'exponential', baseDelayMs, maxDelayMs. The engine retries automatically and emits NODE_FAILED events for each attempt. You get observability without writing retry loops. Timeouts A stuck node can block a run forever. @hazeljs/flow supports per-node timeouts. If a handler exceeds timeoutMs, it's treated as a timeout error (retryable if you have a retry policy). No more orphaned runs. Branching and Conditional Logic Workflows often branch: "if risk score < 30, approve; else if < 70, review; else reject." @hazeljs/flow supports conditional edges with a when(ctx) predicate. Edges are evaluated by priority; if multiple match at the same priority, the engine fails deterministically with AMBIGUOUS_EDGE—no silent wrong-path bugs. Concurrency Safety Multiple workers might tick the same run. With Prisma storage, @hazeljs/flow uses Postgres advisory locks per run: only one process can execute a run at a time; others get LOCK_BUSY and can retry. With in-memory storage, an in-process per-run lock prevents concurrent ticks in the same Node process. No race conditions, no duplicate side effects. Audit Trail Every run has a timeline of events: RUN_STARTED, NODE_STARTED, NODE_FINISHED, NODE_FAILED, RUN_WAITING, RUN_COMPLETED, RUN_ABORTED. You can replay what happened, debug failures, and satisfy compliance requirements. Scenario How flow + flow-runtime help Order & fulfillment pipelines One flow per order: validate → reserve stock → charge → ship → notify. Wait nodes for payment webhooks or warehouse callbacks; resume when events arrive. Durable state and idempotency prevent double charges and lost orders. Approval workflows Expense, PTO, procurement: run starts → wait for approver → resume with payload. No polling or custom state tables. Flow-runtime exposes POST /v1/runs/:runId/resume so your UI or approval service just calls the API. Fraud & risk checks Per transaction: score → branch (approve / review / reject) → optional manual review. Conditional edges and audit timeline support compliance and dispute resolution. Multi-step integrations (ETL, sync) Fetch → transform → write to DB/warehouse → notify on failure. Retries and timeouts for flaky APIs; run as a separate service so other systems trigger runs via HTTP. Document & case workflows Insurance, claims, onboarding: intake → validation → wait for documents → decision → payout. Long-lived runs with waits; state in Postgres so restarts don’t lose progress; timeline for auditors and support. SaaS automation Let customers define or use prebuilt flows; flow-runtime as the backend that runs them. Multi-tenant via tenantId; one shared service, horizontal scaling. Internal ops & support Onboard customer → provision resources → send email → create ticket. Or: alert → triage → assign → wait for resolution → close. One place to see status and history; easy to add steps and retries. Benefit Description Zero config by default In-memory storage out of the box. No database or env vars required to run flows. Framework-agnostic No dependency on Hazel core. Use it with Express, Fastify, NestJS, or plain Node. Decorator-first API Define flows with @Flow, @Entry, @Node, @Edge—familiar to NestJS/Hazel developers. Optional persistence Add Postgres when you need it: install @prisma/client, use createPrismaStorage(prisma) from @hazeljs/flow/prisma. Schema and migrations live in the package. Type-safe Full TypeScript support: FlowContext, NodeResult, typed handlers. Testable Run flows in-process with in-memory storage (or a test DB). No need to spin up a runtime server for unit tests. Optional runtime Use FlowEngine directly in your app, or deploy @hazeljs/flow-runtime as a standalone HTTP service (HazelApp). Invoke it programmatically with runFlowRuntime({ flows, port, databaseUrl?, services }) so apps don’t reimplement the server. Simpler: No separate worker process, no activity/workflow split. Nodes are just async functions. Lighter: Optional Postgres. Start with in-memory; add a single DB when you need durability. No Elasticsearch, Cassandra, or separate Temporal server. Faster to adopt: Define a flow in one file, register it, and run. No SDK concepts to learn. Good for: Teams that want workflow durability without the operational complexity of Temporal. Stateful graphs: BullMQ is job queues; @hazeljs/flow is execution graphs with branching and wait. You model the flow, not just jobs. Built-in wait/resume: No need to "schedule a follow-up job" for human approval. First-class WAIT state. Audit trail: Every transition is recorded (in memory or DB). BullMQ gives you job history; @hazeljs/flow gives you a run timeline. Persistence optional: XState is in-memory. @hazeljs/flow can run in-memory or persist to Postgres when you need crash recovery and multi-process safety. Runtime included: You get an HTTP API (@hazeljs/flow-runtime) and recovery logic. With XState, you build that yourself. Idempotency and retries: Built into the engine, not something you wire up per transition. Self-hosted: No vendor lock-in. Run on your own infra, your own (optional) Postgres. No cold starts: No Lambda limits. Your nodes run in your process or a long-lived runtime. Simpler pricing: You pay for compute (and Postgres if you use it), not per state transition. Option A: In-process, in-memory (no DB) ┌─────────────────────────────────────────────────────────────┐ │ Your Application │ │ ┌─────────────┐ ┌──────────────────┐ │ │ │ FlowEngine │───▶│ createMemoryStorage() │ │ │ (default) │ │ (runs, events, idempotency in memory) │ │ └─────────────┘ └──────────────────┘ │ └─────────────────────────────────────────────────────────────┘ Option B: In-process, with Postgres ┌─────────────────────────────────────────────────────────────┐ │ Your Application │ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ │ FlowEngine │───▶│ createPrismaStorage(prisma) │ │ │ │ │ from @hazeljs/flow/prisma │──▶ Postgres │ └─────────────┘ └──────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────┘ Option C: Standalone HTTP service (@hazeljs/flow-runtime) ┌─────────────────────────────────────────────────────────────┐ │ @hazeljs/flow-runtime (HazelApp) │ │ POST /v1/runs/start │ GET /v1/runs/:id │ POST .../tick │ │ POST /v1/runs/:id/resume │ GET /v1/runs/:id/timeline │ └─────────────────────────────────────────────────────────────┘ │ FlowEngine + (in-memory or Prisma) Use the runtime by running its process (node dist/main.js) or invoke it from your app with runFlowRuntime({ port, databaseUrl?, flows, services })—no need to reimplement the HTTP API. Example: The hazeljs-flow-example repo registers order-processing, approval, fraud-detection, and other flows and starts the server with runFlowRuntime(...). You can run it locally (npm run run:runtime or npm run run:direct) or browse the source for copy-paste patterns. Good fit: Order processing, fulfillment pipelines Approval workflows (expense, PTO, procurement) Fraud detection and review queues Onboarding sequences (signup → verify → onboard) AI agent orchestration (plan → execute → wait for human → continue) Document processing with retries and branching Multi-step integrations and ETL with retries and audit Less ideal: Simple one-off background jobs (use BullMQ or a cron) Real-time streaming (use WebSockets or SSE) High-throughput event sourcing (use Kafka + CQRS) Distributed sagas across many services (consider Temporal) 1. Install (no database required for in-memory) pnpm add @hazeljs/flow 2. Define and run a flow (in-memory) import { FlowEngine, Flow, Entry, Node, Edge, buildFlowDefinition } from '@hazeljs/flow'; import type { FlowContext, NodeResult } from '@hazeljs/flow'; @Flow('order-flow', '1.0.0') class OrderFlow { @Entry() @Node('validate') @Edge('charge') async validate(ctx: FlowContext): Promise { ... } @Node('charge') async charge(ctx: FlowContext): Promise { ... } } const engine = new FlowEngine(); // uses in-memory storage await engine.registerDefinition(buildFlowDefinition(OrderFlow)); const { runId } = await engine.startRun({ flowId: 'order-flow', version: '1.0.0', input: order }); let run = await engine.getRun(runId); while (run?.status === 'RUNNING') { run = await engine.tick(runId); } 3. (Optional) Add Postgres for durability pnpm add @prisma/client # Run migrations from the flow package (see package README) import { FlowEngine } from '@hazeljs/flow'; import { createPrismaStorage, createFlowPrismaClient } from '@hazeljs/flow/prisma'; const prisma = createFlowPrismaClient(process.env.DATABASE_URL); const engine = new FlowEngine({ storage: createPrismaStorage(prisma) }); // same registerDefinition, startRun, tick... 4. (Optional) Run as an HTTP service Use @hazeljs/flow-runtime: run its built-in process with default demo flows, or invoke it from your app with your own flows: import { runFlowRuntime } from '@hazeljs/flow-runtime'; import { myFlow } from './flows'; await runFlowRuntime({ port: 3000, databaseUrl: process.env.DATABASE_URL, // optional; in-memory if omitted flows: [myFlow], services: { logger, slack }, }); @hazeljs/flow gives Node.js developers a durable, auditable, resumable workflow engine without the complexity of Temporal or the limitations of simple job queues. It solves real problems: crash recovery (with optional Postgres), wait-and-resume, idempotency, retries, timeouts, branching, and concurrency—with a decorator-based API and in-memory storage by default, so you can run with zero config and add persistence when you need it. Use FlowEngine in your app or deploy @hazeljs/flow-runtime (HazelApp) as a standalone service; you can also invoke the runtime programmatically with runFlowRuntime({ flows, ... }) so your app stays thin and the package owns the server. Built for developers who need workflows that don't break. Packages: @hazeljs/flow · @hazeljs/flow-runtime Source: hazeljs monorepo — packages/flow and packages/flow-runtime Examples: hazeljs-flow-example — full runnable app with flows, runtime, and HTTP client; hazeljs/example — in-repo flow examples (src/flow/) Docs: HazelJS