Flow Runtime Package
The @hazeljs/flow-runtime package provides a standalone HTTP service (HazelApp) that runs @hazeljs/flow workflows. Expose flows via REST so other systems can start runs, tick, resume waiting runs, and read status and timeline—without implementing the server yourself.
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.