The variable engine has many abstract concepts. This article uses 🌟 to mark a batch of concepts that you can prioritize understanding.
The core concepts of the variable engine can be summarized in the following diagram:
See Variable Introduction for details.
In process design, variables only focus on definitions, not values. The value of a variable is dynamically calculated at the process's runtime.
A Scope is a container: it aggregates a series of variable information and maintains dependencies with other scopes.
The range of a scope can be defined according to different business scenarios:
| Scene | Example |
|---|---|
| Nodes in a process can be defined as scopes | ![]() |
| The global variable sidebar can also be defined as a scope | ![]() |
| Components (including variables) in UI editing can be defined as scopes | ![]() |
A Scope stores variable information through an AST.
You can access the AST tree within a scope via scope.ast to perform CRUD operations on variable information.
ASTNode is the basic information unit used in the variable engine to store variable information. It can model various variable information, including:
VariableDeclaration, used to declare new variables.StringType, used to represent the String type.KeyPathExpression, used to reference variables.ASTNode can be nested to form a tree (AST) to represent complex variable structures.ASTNode can be converted to and from JSON format (ASTNodeJSON) for storage or transmission.ASTNode base class.ASTNode values trigger events, enabling a reactive programming model.ASTNodeJSON is the pure JSON serialization representation of an ASTNode.
ASTNodeJSON includes a kind field to indicate the type of the ASTNode:
When using the variable engine, users describe variable information with ASTNodeJSON, which is then instantiated into an ASTNode by the variable engine and added to the scope.
The relationship between ASTNodeJSON and ASTNode is similar to the relationship between JSX and VDOM in React.
ASTNodeJSON is instantiated into ASTNode by the variable engine.JSX is instantiated into VDOM by the React engine.Json Schema is a format for describing the structure of JSON data:
Json Schema only describes the type information of a variable, while ASTNodeJSON can also contain other information about the variable (e.g., its initial value).ASTNodeJSON can be instantiated into an ASTNode by the variable engine, enabling capabilities like reactive listening.Json Schema is good at describing Json types, while ASTNodeJSON can define more complex behaviors through custom extensions.In terms of technical selection, the VariableEngine requires more powerful extension and expression capabilities. Therefore, ASTNodeJSON is needed to describe richer and more complex variable information, such as implementing dynamic type inference and automatic linking by defining the initial value of variables.
However, as an industry-standard format for describing JSON types, Json Schema has advantages in ease of use, cross-team communication, and ecosystem (e.g., ajv, zod). Therefore, we use Json Schema extensively in our Materials to lower the barrier to entry.
The variable engine provides ASTFactory for type-safe creation of ASTNodeJSON:
Declaration = Identifier (Key) + Definition. In design mode, a declaration is an ASTNode that stores an identifier and its definition.
Variable Declaration = Identifier + Variable Definition (Type + Initial Value)
Function Declaration = Identifier + Function Definition (Function Parameters and Return Value + Function Body Implementation)
Struct Declaration = Identifier + Struct Definition (Fields + Types)
Identifier is the index of a declaration, used to access the Definition within the declaration.Identifier to find the type Definition of a variable for type checking.The variable engine currently only provides variable field declaration (BaseVariableField), and extends it to two types of declarations: variable declaration (VariableDeclaration) and property declaration (Property).
BaseVariableField) = Identifier + Variable Field Definition (Type + Metadata + Initial Value)VariableDeclaration) = Globally Unique Identifier + Variable Definition (Type + Metadata + Initial Value + Order within Scope)Property) = Unique within Object Identifier + Property Definition (Type + Metadata + Initial Value)Types are used to constrain the range of variable values. In design mode, a type is an ASTNode.
The variable engine has built-in basic types from JSON:
StringType: stringIntegerType: integerNumberType: floating-point numberBooleanType: booleanObjectType: object, which can be drilled down into Property declarations.ArrayType: array, which can be drilled down into other types.It also adds:
MapType: key-value pairs, where both keys and values can have type definitions.CustomType: can be custom extended by the user, such as date, time, file types, etc.An expression takes 0 or more variables as input, computes them in a specific way, and returns a new variable.
In design mode, an expression is an ASTNode. In modeling, we only need to focus on:
Suppose we have an expression described in JavaScript code as ref_var + 1.
Which variable declarations does the expression use?
ref_var identifier.How is the expression's return type inferred?
ref_var is IntegerType, the return type of ref_var + 1 is IntegerType.ref_var is NumberType, the return type of ref_var + 1 is NumberType.ref_var is StringType, the return type of ref_var + 1 is StringType.
The figure shows a common example: a batch processing node references the output variable of a preceding node, iterates over it, and obtains an item variable. The type of the item variable automatically changes with the type of the output variable of the preceding node.
The ASTNodeJSON for this example can be represented as:
The type inference chain is as follows:
The Scope Chain defines which scopes' variables a scope can reference. It is an abstract class, and specific business scenarios can implement custom scope chains.
The variable engine has built-in implementations for two types of scope chains: free-layout scope chain and fixed-layout scope chain.
Dependency Scope = Which scopes' output variables a scope can access.
You can access a scope's Dependency Scope via scope.depScopes.
Covering Scope = Which scopes can access the output variables of a scope.
You can access a scope's Covering Scope via scope.coverScopes.
FlowGram defines the following special types of scopes in the canvas:
Also known as Node Public Scope, this scope can access the variables of the Node Scope of upstream nodes, and its output variable declarations can also be accessed by the Node Scope of downstream nodes.
The Node Scope can be set and retrieved via node.scope. Its scope chain relationship is shown in the figure below:
In the default scope logic, the output variables of a child node's Node Scope cannot be accessed by the downstream nodes of the parent node.
The output variables of a Node Private Scope can only be accessed within the current node's Node Scope and its child nodes' Node Scope. This is similar to the concept of private variables in programming languages.
The Node Private Scope can be set and retrieved via node.privateScope. Its scope chain relationship is shown in the figure below:
Variables in the Global Scope can be accessed by all node scopes and node private scopes, but it cannot access variables from other scopes.
For how to set the global scope, see Output Global Variables. Its scope chain relationship is shown in the figure below:

The variable engine is designed following the DIP (Dependency Inversion Principle) and is divided into three layers according to code stability, abstraction level, and proximity to the business:
This is the highest abstraction level in the variable architecture and the most stable part of the code. The abstraction layer defines abstract classes for core concepts such as ASTNode, Scope, and ScopeChain.
This part of the variable architecture changes more frequently and may be adjusted for different business needs. The engine has a built-in set of stable ASTNode implementations and ScopeChain implementations. When users have complex variable requirements, they can register new ASTNodes or override existing ASTNodes through dependency injection for customization.
Based on the Facade pattern, this layer improves the usability of variables by encapsulating complex variable logic into simple, out-of-the-box variable materials.