HazelJS Flow Runtime Package
@hazeljs/flow-runtime provides a standalone HTTP service that runs @hazeljs/flow workflows, exposing them via REST for starting, ticking, resuming, and monitoring runs.
Quick Reference
- Purpose:
@hazeljs/flow-runtimewraps@hazeljs/flowin a standalone HTTP server, exposing flow workflows via REST endpoints for external systems to start runs, tick, resume waiting runs, and read status/timeline. - When to use: Use
@hazeljs/flow-runtimewhen workflows need to be triggered and managed via HTTP from external systems. Use@hazeljs/flowdirectly for in-process workflow execution. - Key concepts: Standalone HazelApp server, REST endpoints for flow management, run lifecycle (start, tick, resume, status, timeline).
- Dependencies:
@hazeljs/core,@hazeljs/flow. - Common patterns: Define flows with
@hazeljs/flow→ deploy@hazeljs/flow-runtimeas a service → external systems call REST endpoints to manage workflow runs. - Common mistakes: Not configuring persistent storage for production (runs lost on restart); exposing flow runtime without authentication.
Purpose
When you build workflows with @hazeljs/flow, you can either:
- In-process — Use
FlowEnginedirectly in your app (same process) - Standalone service — Run the flow-runtime process and call its HTTP API
- Programmatic — Call
runFlowRuntime({ flows, port, databaseUrl?, services })from your app so the package owns the server and you just pass your flows
The flow-runtime package gives you:
- REST API — Start runs, tick, resume, get run status and timeline, list flows
- Recovery — On startup, picks up
RUNNINGruns and ticks them (with Prisma storage) - Optional Postgres — Pass
databaseUrlfor durable storage; omit for in-memory - Custom flows — Register your own flow definitions and optional services (logger, slack, etc.)
Architecture
graph LR A["Client / UI / Service"] -->|HTTP| B["Flow Runtime (HazelApp)"] B --> C["FlowEngine"] C --> D["In-Memory or Prisma"] D --> E["Postgres (optional)"] B --> F["Recovery (RUNNING runs)"] style A fill:#3b82f6,stroke:#60a5fa,color:#fff style B fill:#6366f1,stroke:#818cf8,color:#fff style C fill:#8b5cf6,stroke:#a78bfa,color:#fff style D fill:#10b981,stroke:#34d399,color:#fff
- Clients call the runtime's REST endpoints
- Flow runtime uses
FlowEnginefrom@hazeljs/flowwith in-memory or Prisma storage - Recovery runs on startup when using Prisma to continue any runs that were
RUNNINGwhen the process stopped
Installation
pnpm add @hazeljs/flow-runtime @hazeljs/flow
@hazeljs/flow is required; flow-runtime wraps it and exposes the HTTP API.
Running the runtime
As a standalone process
# From the flow-runtime package
pnpm dev
# or
pnpm start # after build
Requires PORT (default 3000) and optionally DATABASE_URL for Postgres. If DATABASE_URL is missing or the connection fails, the runtime falls back to in-memory storage.
From your app (programmatic)
Invoke the runtime from your application so you register your own flows and services—no need to reimplement the HTTP API:
import { runFlowRuntime } from '@hazeljs/flow-runtime';
import { buildFlowDefinition } from '@hazeljs/flow';
import { OrderFlow } from './flows/OrderFlow';
await runFlowRuntime({
port: 3000,
databaseUrl: process.env.DATABASE_URL, // optional; in-memory if omitted
flows: [buildFlowDefinition(OrderFlow)],
services: { logger: myLogger, slack: slackClient },
});
The server listens on port and runs recovery on startup when using Prisma storage.
API Reference
| Method | Path | Description |
|---|---|---|
POST | /v1/runs/start | Start a new flow run (body: flowId, version, input) |
POST | /v1/runs/:runId/tick | Advance a running run one step |
POST | /v1/runs/:runId/resume | Resume a waiting run (body: payload for the wait) |
GET | /v1/runs/:runId | Get run status and current state |
GET | /v1/runs/:runId/timeline | Get event timeline for the run |
GET | /v1/flows | List registered flow definitions |
GET | /health | Health check |
Start a run
curl -X POST http://localhost:3000/v1/runs/start \
-H "Content-Type: application/json" \
-d '{"flowId":"order-flow","version":"1.0.0","input":{"orderId":"ORD-123"}}'
Response includes runId. Use it to tick or fetch status.
Resume a waiting run
When a node returns { status: 'wait', reason: '...' }, the run stays in WAITING. To continue:
curl -X POST http://localhost:3000/v1/runs/:runId/resume \
-H "Content-Type: application/json" \
-d '{"approved":true}'
Get run status and timeline
curl http://localhost:3000/v1/runs/:runId
curl http://localhost:3000/v1/runs/:runId/timeline
Configuration
| Env / option | Description |
|---|---|
PORT | HTTP port (default 3000) |
DATABASE_URL | Optional Postgres URL; if unset or connection fails, uses in-memory storage |
When using Postgres, run the flow package's Prisma migrations so the runtime can persist runs and events.
Recovery
On startup, the runtime calls engine.getRunningRunIds() and ticks each run (with concurrency limit). This continues any run that was RUNNING when the process last stopped, so you get crash recovery without manual scripts.
Example repo
The hazeljs-flow-example repo includes order-processing, approval, and fraud-detection flows and shows how to start the runtime with runFlowRuntime(...) and call the API from a client.
What's next?
- Flow — Define workflows with decorators, persistence, wait/resume, and idempotency
- Prisma — Optional persistence for flow-runtime uses the flow package's Prisma schema; use the same patterns for your app data
Recipes
Recipe: Start Flow Runtime Programmatically
// File: src/main.ts
import { runFlowRuntime } from '@hazeljs/flow-runtime';
import { OrderFlow } from './flows/order.flow';
import { ApprovalFlow } from './flows/approval.flow';
runFlowRuntime({
flows: [OrderFlow, ApprovalFlow],
port: 4000,
databaseUrl: process.env.DATABASE_URL,
});
// POST http://localhost:4000/flows/order-processing/start → starts a run
// GET http://localhost:4000/flows/order-processing/runs/:id → get run status
Recipe: Trigger a Flow via REST from Another Service
// File: src/integration/flow-client.service.ts
import { Service } from '@hazeljs/core';
@Service()
export class FlowClientService {
private readonly baseUrl = process.env.FLOW_RUNTIME_URL || 'http://localhost:4000';
async startOrderFlow(orderId: string, items: any[]) {
const res = await fetch(`${this.baseUrl}/flows/order-processing/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, items }),
});
return res.json();
}
async getRunStatus(runId: string) {
const res = await fetch(`${this.baseUrl}/flows/order-processing/runs/${runId}`);
return res.json();
}
}
Related Resources
- Flow Package – Workflow definition and execution
- Prisma Package – Database persistence
- Cron Package – Scheduled flow triggers
- hazeljs-flow-example – Complete flow examples