This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Backend (Spring Boot 3.5.11 / Java 21 / Maven):
./mvnw install # install dependencies
./mvnw package # build JAR
./mvnw spring-boot:run # run dev server
./mvnw test # run testsFrontend (Vue 3 / Vite, in web/):
cd web
npm install
npm run dev # dev server with hot reload
npm run build # production buildRequires MySQL and Redis. Configure both in src/main/resources/application.yaml.
This is a visual workflow engine: users compose DAG-based flowcharts in a Vue Flow frontend, submit them to the Spring Boot backend, which executes nodes concurrently via virtual threads and streams results back via SSE.
- WorkflowHandler — entry point. Receives
WorkflowVO, converts toWorkflowDTO, starts ResultHandler and NodeHandler threads, then exits (non-blocking). - NodeHandler — walks the graph from the start node. For each node: runs
before()→run()→after()hooks, then spawns a new NodeHandler per downstream node for parallel branch execution. - ResultHandler — polls the result queue and streams results to the client via SSE (
SseHandler).
All state lives in GlobalPool, backed by Redis. States are bitwise hex flags — check with & operator, never equality:
(state & WorkflowStates.RUNNING) != 0 // correct
state == WorkflowStates.RUNNING // wrongState classes: WorkflowStates, NodeStates, ResultHandlerStates. -1 is reserved NULL, 0x1 is reserved ERROR. New states must be hex, ordered by execution sequence, and use left-shift where possible.
All nodes extend NodeImpl (in utils.workflow.nodes). Three hooks to override:
before()— load configs, validate, callonNodeError()oronNodeDisabled()to abortrun()— core logic, write results viaglobalPool.pushWorkflowResult(), write variables tonodePool.put()(NOT directly to globalPool)after()— cleanup, filter results
To add a new node:
- Create class extending
NodeImplin appropriate package (base/,collections/list/,collections/map/) - Add enum entry in
NodeTypewith hex code (use| NESTABLE_FLAGif it can contain sub-nodes) - Register class in
NodeType.nodeClazzMapstatic block - Override
getNodeConfigs()andgetNodeOutputs()for frontend discovery
Config types (see ConfigTypes): NUMBER, SLIDER, STRING, BOOLEAN, LIST, MAP, CONDITION. Variable injection uses {{nodeId:variableName}} syntax in string configs — GlobalPool resolves these at parse time.
- GlobalPool (Redis): shared across all nodes in a workflow, keyed by workflow token
- NodePool: per-node, merged into GlobalPool during
after()withnodeId:prefix on keys - Never modify state directly — always go through GlobalPool methods
REST base path: /api/v3. Key endpoints:
POST /workflow/run— execute workflow (returns SSE emitter)POST /workflow— save workflowGET /node/types— list all node typesGET /node/{code}/config— get node config schemaGET /node/{code}/output— get node output variable descriptors
Vue 3 + Vite + Vue Flow (DAG editor) + Ant Design Vue. Node types rendered as custom Vue Flow nodes. SSE client uses @microsoft/fetch-event-source. Node.js >= 20.19 required.