A browser tool that shows what happens when API calls fail — retry, wait time (backoff), and timeout.
This is not Zapier. This is not Kubernetes. Flow-Craft answers one question: when an API call fails, what happens next?
Open the live demo in your browser — no install needed.
Open the app. You will see a ready-made workflow: Webhook → HTTP Request. Choose a failure scenario, click Run, and watch the Execution Timeline:
✓ Webhook succeeded +0ms
… HTTP Request started +2ms
✕ HTTP Request failed — HTTP 429 +2ms
⏳ Waiting 2000ms before retry +2ms
↻ HTTP Request — retry #2 +2004ms
✓ HTTP Request succeeded (200) +2004ms
✓ Execution success +2004ms
The numbers with + show elapsed time since the run started (not how long each step took).
Click any HTTP step to open the Node Inspector. You can see input, output, number of attempts, duration, error, and retry settings.
- Open the live demo.
- Keep the default workflow.
- Select 429 Rate Limit.
- Click Run.
- Watch the request fail, wait for
Retry-After, try again, and succeed. - Click a step in the timeline to see details.
This flow — 429 → Retry-After → Backoff → Retry → Success → Inspector — is the main idea of this project.
The engine in src/engine/ is plain TypeScript. It does not use Vue. Vue only shows what the engine produces.
flowchart LR
workflow[Webhook to HTTP]
engine[Execution Engine]
port[HttpPort]
scenario[Selected scenario]
events[Event stream]
inspector[Execution Timeline and Node Inspector]
workflow --> engine
engine --> port
scenario -.injected into.-> port
port --> events
events --> inspector
Important design choices (each has a short ADR document in docs/adr/):
- Why everything runs in the browser — no backend, no real
fetch(). The engine only talks toHttpPort. It does not know if the response is fake or real. - Execution model and event list — status is
pending | running | success | failed. There is noRETRYINGorcancelled. The UI reads a simple list of events. - Retry and backoff rules — the
Retry-Afterheader wins over calculated backoff. Jitter uses random numbers, but the demo uses a fixed seed so the same scenario gives the same timeline every time. - Why a simple linear workflow — only two node types (
trigger,http). No edges. Array order = run order. No drag-and-drop canvas.
npm test # watch mode
npm run test:runWhat the tests cover:
runHttpNodeWithRetry— success on first try; retry after 429;Retry-Afteroverrides backoff; exponential backoff without header; jitter; stop atmaxAttempts; no retry for bad status codes; no retry after timeout.runWorkflow— runs nodes in order; a failed node stops later nodes.parseRetryAfter— seconds format and date format, including past dates.computeBackoffDelay— fixed random source; doubling without jitter.withTimeout— task finishes in time, or throwsTimeoutError.projectNodeSummary— builds attempt count, duration, input/output, and status from events.validateWorkflow— all validation rules (name, unique ids, trigger, HTTP node, timeout > 0, at least one attempt).
Fake Clock, Scheduler, and RandomSource live in tests/helpers/. A test for "wait 2 seconds before retry" runs in milliseconds, not real seconds.
src/
├── engine/ executor, retry, timeout, clock, scheduler, random, backoff, retryAfter
├── ports/ HttpPort interface + createHttpPort(scenario)
├── scenarios/ normal, rateLimit, timeout
├── validation/ workflowValidator
├── types/ workflow, execution
├── composables/ useWorkflowRun — small Vue wrapper around the engine
├── components/ WorkflowEditor, ScenarioSelector, ExecutionTimeline, NodeInspector
├── defaultWorkflow.ts
└── App.vue
tests/
├── engine/
├── validation/
└── helpers/ fakes and fixtures for tests
docs/
└── adr/ architecture decision records
npm install
npm run dev
npm run lint # ESLint
npm run lint:fix # auto-fix where possiblenpm run buildGitHub Pages base path is /Flow-Craft/. Push to master and the GitHub Actions workflow (.github/workflows/deploy.yml) lints, tests, builds, and deploys dist/ automatically.
MIT