Browse Source

docs(variable): variable concepts (#933)

* docs(variable): add description for docs

* chore: variable output docs optimize

* docs(variable): rewrite concept of variable
Yiwei Mao 2 tháng trước cách đây
mục cha
commit
4275b583cd
27 tập tin đã thay đổi với 1423 bổ sung336 xóa
  1. 0 1
      apps/docs/src/en/guide/concepts/_meta.json
  2. 0 84
      apps/docs/src/en/guide/concepts/variable-engine.mdx
  3. 1 0
      apps/docs/src/en/guide/variable/_meta.json
  4. 17 7
      apps/docs/src/en/guide/variable/basic.mdx
  5. 506 0
      apps/docs/src/en/guide/variable/concept.mdx
  6. 21 17
      apps/docs/src/en/guide/variable/variable-consume.mdx
  7. 45 57
      apps/docs/src/en/guide/variable/variable-output.mdx
  8. BIN
      apps/docs/src/public/variable/concept/arch-en.png
  9. BIN
      apps/docs/src/public/variable/concept/arch-zh.png
  10. BIN
      apps/docs/src/public/variable/concept/concepts-en.png
  11. BIN
      apps/docs/src/public/variable/concept/concepts-zh.png
  12. BIN
      apps/docs/src/public/variable/concept/scope-1.png
  13. BIN
      apps/docs/src/public/variable/concept/scope-2.png
  14. BIN
      apps/docs/src/public/variable/concept/scope-3.png
  15. 0 1
      apps/docs/src/zh/guide/concepts/_meta.json
  16. 0 86
      apps/docs/src/zh/guide/concepts/variable-engine.mdx
  17. 1 0
      apps/docs/src/zh/guide/variable/_meta.json
  18. 19 10
      apps/docs/src/zh/guide/variable/basic.mdx
  19. 3 0
      apps/docs/src/zh/guide/variable/cases/_meta.json
  20. 5 0
      apps/docs/src/zh/guide/variable/cases/case-batch-variable.mdx
  21. 506 0
      apps/docs/src/zh/guide/variable/concept.mdx
  22. 16 0
      apps/docs/src/zh/guide/variable/core-api.mdx
  23. 207 0
      apps/docs/src/zh/guide/variable/core-ast.mdx
  24. 16 0
      apps/docs/src/zh/guide/variable/custom-scope-chain.mdx
  25. 14 9
      apps/docs/src/zh/guide/variable/variable-consume.mdx
  26. 35 56
      apps/docs/src/zh/guide/variable/variable-output.mdx
  27. 11 8
      packages/plugins/node-variable-plugin/src/form-v2/create-provider-effect.ts

+ 0 - 1
apps/docs/src/en/guide/concepts/_meta.json

@@ -1,7 +1,6 @@
 [
 [
   "canvas-engine",
   "canvas-engine",
   "node-engine",
   "node-engine",
-  "variable-engine",
   "ecs",
   "ecs",
   "ioc",
   "ioc",
   "reactflow"
   "reactflow"

+ 0 - 84
apps/docs/src/en/guide/concepts/variable-engine.mdx

@@ -1,84 +0,0 @@
-# Variable Engine
-
-## Overall Design
-
-### Architecture Layers
-
-:::warning Architecture Layers
-The variable engine is designed to follow the DIP (Dependency Inversion Principle). It is divided into three layers according to code stability, abstraction level, and proximity to the business:
-- Variable Abstraction Layer: The part with the highest level of abstraction and the most stable code in the variable architecture.
-- Variable Implementation Layer: The part that changes significantly and usually requires adjustments between different businesses in the variable architecture.
-- Variable Business Layer: The Facade provided to the business in the variable architecture, which interacts with the canvas engine and node engine.
-
-:::
-
-![Architecture Layers Diagram](@/public/en-variable-engine.png)
-
-### Glossary
-
-#### 🌟 Scope
-:::warning ⭐️⭐️⭐️ Definition:
-A predefined space where the declaration and consumption of variables are described through an AST.
-- Predefined space: The nature of the space is entirely defined by the business.
-- In the low-code design state, it can be a node, a component, a right-side panel, etc.
-- In a piece of code, it can be a single statement, a code block, a function, a file, etc.
-
-:::
-
-What the scope space is can be defined by different businesses.
-
-#### 🌟 Abstract Syntax Tree (AST)
-
-:::warning Definition:
-⭐️⭐️⭐️ A protocol that combines AST nodes in a tree structure to explicitly or implicitly perform CRUD operations on variable information.
-- AST nodes: Reactive protocol nodes in the AST.
-- Explicit CRUD, e.g., the business explicitly sets the variable type of a variable.
-- Implicit CRUD, e.g., the business declares a variable, and the variable type is automatically inferred based on its initialization parameters.
-
-:::
-
-:::warning Variable information such as variables, types, expressions, and structures within the scope... are essentially combinations of AST nodes.
-- Variable -> VariableDeclaration node
-- Expression -> Expression node
-- Type -> TypeNode node
-- Structure -> StructDeclaration node
-
-:::
-
-Reference link: https://ts-ast-viewer.com/
-
-#### Variable
-
-:::warning Definition:
-An AST node used to declare a new variable, which points to a value that changes within a specific set of ranges through a unique identifier.
-- A value that changes within a specific set of ranges: The value of the variable must be within the range described by the variable type.
-- Unique identifier: The variable must have a unique key value.
-
-:::
-
-![Variables in JavaScript, with a unique key + pointing to a changing value](@/public/variable-code.png)
-
-#### Variable Type
-
-:::warning Definition:
-⭐️⭐️⭐️ An AST node used to constrain a variable. The value of the constrained variable can only change within a pre - set set of ranges.
-- A variable can be bound to a variable type.
-
-:::
-<table>
-  <tr>
-    <td><img loading="lazy" src="/en-variable-type1.png"/></td>
-    <td><img loading="lazy" src="/variable-type2.png"/></td>
-  </tr>
-</table>
-
-### An Intuitive Understanding of the Variable Engine
-
-:::warning Imagine a world of the variable engine:
-- Different scopes are used to define different "countries".
-- Each "country" contains three main "citizens": declarations, types, and expressions.
-- Communication between "countries" is achieved through the scope chain.
-
-:::
-
-![Illustration](@/public/en-varaible-zone.png)

+ 1 - 0
apps/docs/src/en/guide/variable/_meta.json

@@ -1,5 +1,6 @@
 [
 [
   "basic",
   "basic",
+  "concept",
   "variable-output",
   "variable-output",
   "variable-consume"
   "variable-consume"
 ]
 ]

+ 17 - 7
apps/docs/src/en/guide/variable/basic.mdx

@@ -1,4 +1,14 @@
-# Variable Basics
+---
+description: What is Variable? And why Variable Engine needed?
+---
+
+# Introduction
+
+:::warning
+
+The VariableEngine in this document belongs to the **Design** of FlowGram, which is different from the VariableEngine in the Runtime.
+
+:::
 
 
 ## What is a Variable?
 ## What is a Variable?
 
 
@@ -9,7 +19,7 @@ Simply put, a variable is a named container where you can store various things,
 A variable typically consists of three parts:
 A variable typically consists of three parts:
 
 
 - **Name (Unique Identifier)**: Like your name, it allows everyone to find this variable accurately. For example, `userName`, `orderId`.
 - **Name (Unique Identifier)**: Like your name, it allows everyone to find this variable accurately. For example, `userName`, `orderId`.
-- **Value**: The content inside the container. It can be a number `123`, text `"Hello Flowgram!"`, or a switch state `true` / `false`.
+- **Value**: The content inside the container. It can be a number `123`, text `"Hello FlowGram!"`, or a switch state `true` / `false`.
 - **Type**: Specifies what kind of things this container can hold. For instance, some can only hold numbers, while others can only hold text.
 - **Type**: Specifies what kind of things this container can hold. For instance, some can only hold numbers, while others can only hold text.
 
 
 ---
 ---
@@ -17,7 +27,7 @@ A variable typically consists of three parts:
 For example, in an "Intelligent Q&A" flow:
 For example, in an "Intelligent Q&A" flow:
 
 
 <div style={{display: 'flex', gap: '20px'}}>
 <div style={{display: 'flex', gap: '20px'}}>
-  <img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
+  <img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" alt="Smart Q&A Flow" />
   <div>
   <div>
     <p style={{marginTop: 10}}>1. **`WebSearch` Node**: Responsible for searching the web and putting the found knowledge (e.g., the answer to "What's the weather like today?") into a variable named `natural_language_desc`.</p>
     <p style={{marginTop: 10}}>1. **`WebSearch` Node**: Responsible for searching the web and putting the found knowledge (e.g., the answer to "What's the weather like today?") into a variable named `natural_language_desc`.</p>
     <p style={{marginTop: 5}}>2. **`LLM` Node**: It takes the `natural_language_desc` "messenger," reads its content, and then answers the user in a more natural and friendly way.</p>
     <p style={{marginTop: 5}}>2. **`LLM` Node**: It takes the `natural_language_desc` "messenger," reads its content, and then answers the user in a more natural and friendly way.</p>
@@ -29,7 +39,7 @@ For example, in an "Intelligent Q&A" flow:
 
 
 As the complexity of workflows increases, so do the number and management difficulty of variables.
 As the complexity of workflows increases, so do the number and management difficulty of variables.
 
 
-To address this challenge, Flowgram provides a powerful **Variable Engine**.
+To address this challenge, FlowGram provides a powerful **Variable Engine**.
 
 
 It acts like a professional "data steward," systematically managing all variables to ensure the clarity and stability of the data flow.
 It acts like a professional "data steward," systematically managing all variables to ensure the clarity and stability of the data flow.
 
 
@@ -41,11 +51,11 @@ Enabling the Variable Engine will bring you the following core advantages:
     <p className="rs-tip">The Variable Engine can precisely control the effective range (i.e., scope) of each variable. Like having specific keys for different rooms, it ensures that variables are only accessed within the intended nodes, effectively preventing data pollution and unexpected logical errors.</p>
     <p className="rs-tip">The Variable Engine can precisely control the effective range (i.e., scope) of each variable. Like having specific keys for different rooms, it ensures that variables are only accessed within the intended nodes, effectively preventing data pollution and unexpected logical errors.</p>
     <div style={{display: "flex", gap: "25px"}}>
     <div style={{display: "flex", gap: "25px"}}>
       <div>
       <div>
-        <img loading="lazy" src="/variable/variable-scope-feature-1.png" />
+        <img loading="lazy" src="/variable/variable-scope-feature-1.png" alt="Scope Constraint Example 1" />
         <p style={{marginTop: '10px'}}>The `query` variable defined in the `Start` node can be easily accessed by the subsequent `LLM` and `End` nodes.</p>
         <p style={{marginTop: '10px'}}>The `query` variable defined in the `Start` node can be easily accessed by the subsequent `LLM` and `End` nodes.</p>
       </div>
       </div>
       <div>
       <div>
-        <img loading="lazy" src="/variable/variable-scope-feature-2.png" />
+        <img loading="lazy" src="/variable/variable-scope-feature-2.png" alt="Scope Constraint Example 2" />
         <p style={{marginTop: '10px'}}>The `LLM` node is in a `Condition` branch, like being in a separate room. The `End` node outside naturally cannot access its `result` variable.</p>
         <p style={{marginTop: '10px'}}>The `LLM` node is in a `Condition` branch, like being in a separate room. The `End` node outside naturally cannot access its `result` variable.</p>
       </div>
       </div>
     </div>
     </div>
@@ -53,7 +63,7 @@ Enabling the Variable Engine will bring you the following core advantages:
   <div>
   <div>
     <b>Variable Structure Insight: Easily Understand Complex Data</b>
     <b>Variable Structure Insight: Easily Understand Complex Data</b>
     <p className="rs-tip">When a variable becomes complex (e.g., an object with many levels), the Variable Engine allows you to explore its internal structure layer by layer, like peeling an onion, with all details at your fingertips.</p>
     <p className="rs-tip">When a variable becomes complex (e.g., an object with many levels), the Variable Engine allows you to explore its internal structure layer by layer, like peeling an onion, with all details at your fingertips.</p>
-    <img loading="lazy" src="/variable/variable-tree-management.gif" />
+    <img loading="lazy" src="/variable/variable-tree-management.gif" alt="Variable Structure Perspective" />
     <p style={{marginTop: '10px'}}>In this diagram, you can see the output variables of all nodes and their hierarchical relationships, like a lush tree.</p>
     <p style={{marginTop: '10px'}}>In this diagram, you can see the output variables of all nodes and their hierarchical relationships, like a lush tree.</p>
   </div>
   </div>
   <div>
   <div>

+ 506 - 0
apps/docs/src/en/guide/variable/concept.mdx

@@ -0,0 +1,506 @@
+---
+description: Introduces the core concepts of the variable engine.
+---
+
+# Concepts
+
+
+:::tip
+
+The variable engine has many abstract concepts. This article uses 🌟 to mark a batch of concepts that you can **prioritize understanding**.
+
+:::
+
+:::info{title="📖 Quick Terminology Lookup"}
+
+- [**Variable**](#variable) 🌟
+- [**Scope**](#scope-) 🌟: A container that aggregates a series of variable information and maintains dependencies with other scopes.
+- [**AST**](#ast-) 🌟: Scopes use AST to store variable information.
+- [**ASTNode**](#astnode): The basic unit for storing variable information.
+- [**ASTNodeJSON**](#astnodejson): The JSON representation of an ASTNode.
+- [**Declaration**](#declaration) 🌟: Identifier + Definition.
+- [**Type**](#type) 🌟: Constraints on the value of a variable.
+- [**Expression**](#expression): Combines several variables in a specific way to return a new variable.
+- [**Scope Chain**](#scope-chain): Defines which scopes' variables a scope can reference.
+- [**Dependency Scope**](#dependency-scope): Which scopes' output variables a scope can access.
+- [**Covering Scope**](#covering-scope): Which scopes can access the output variables of a scope.
+- [**Node Scope**](#node-scope) 🌟: Can access the output variables of upstream nodes, and its output variables can also be accessed by downstream nodes.
+- [**Node Private Scope**](#node-private-scope): The node's private scope can only be accessed by the node itself or its child nodes.
+- [**Global Scope**](#global-scope): Variables in the global scope can be accessed by all node scopes.
+
+:::
+
+
+
+## Core Concepts
+
+The core concepts of the variable engine can be summarized in the following diagram:
+
+<img src="/variable/concept/concepts-en.png" alt="Variable Core Concepts Relationship Diagram" width="600" />
+
+
+### Variable
+
+See [Variable Introduction](./basic.mdx) for details.
+
+:::warning{title="⚠️ Different Focus on Variables in Design and Runtime"}
+
+**In process design, variables only focus on definitions, not values**. The value of a variable is dynamically calculated at the process's [runtime](/guide/runtime/introduction).
+
+:::
+
+### Scope 🌟
+
+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 | <img src="/variable/concept/scope-1.png" alt="Node Scope" width="600" /> |
+| The global variable sidebar can also be defined as a scope | <img src="/variable/concept/scope-2.png" alt="Global Scope" width="600" /> |
+| Components (including variables) in UI editing can be defined as scopes | <img src="/variable/concept/scope-3.png" alt="Component Scope" width="600" /> |
+
+
+
+:::warning{title="Why does FlowGram abstract the concept of a scope outside of nodes?"}
+
+1. A node is not equivalent to a scope.
+2. Some scopes (e.g., global scope) are not related to nodes.
+3. A node can have multiple scopes (e.g., loop private scope).
+
+:::
+
+### AST 🌟
+
+A Scope stores variable information through an `AST`.
+
+:::tip
+
+You can access the `AST` tree within a scope via `scope.ast` to perform CRUD operations on variable information.
+
+:::
+
+
+#### ASTNode
+
+`ASTNode` is the **basic information unit** used in the variable engine to **store variable information**. It can model various **variable information**, including:
+
+- **Declarations**: such as `VariableDeclaration`, used to declare new variables.
+- **Types**: such as `StringType`, used to represent the String type.
+- **Expressions**: such as `KeyPathExpression`, used to reference variables.
+
+:::info{title="ASTNode has the following features"}
+
+- **Tree Structure**: `ASTNode` can be nested to form a tree (`AST`) to represent complex variable structures.
+- **Serialization**: `ASTNode` can be converted to and from JSON format (`ASTNodeJSON`) for storage or transmission.
+- **Extensibility**: New features can be added by extending the `ASTNode` base class.
+- **Reactivity**: Changes in `ASTNode` values trigger events, enabling a reactive programming model.
+
+:::
+
+#### ASTNodeJSON
+
+`ASTNodeJSON` is the **pure JSON serialization** representation of an `ASTNode`.
+
+`ASTNodeJSON` includes a `kind` field to indicate the type of the `ASTNode`:
+
+```tsx
+/**
+ * Equivalent to the JavaScript code:
+ * `var var_index: string`
+ */
+{
+  kind: 'VariableDeclaration',
+  key: 'var_index',
+  type: { kind: 'StringType' },
+}
+```
+
+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.
+
+```tsx
+/**
+ * Instantiate ASTNodeJSON into an ASTNode and add it to the scope using the scope.setVar method
+ */
+const variableDeclaration: VariableDeclaration = scope.setVar({
+  kind: 'VariableDeclaration',
+  key: 'var_index',
+  type: { kind: 'StringType' },
+});
+
+/**
+ * After ASTNodeJSON is instantiated into an ASTNode, you can listen for changes reactively
+ */
+variableDeclaration.onTypeChange((newType) => {
+  console.log('Variable type changed', newType);
+})
+
+```
+
+:::info{title="Concept Comparison"}
+
+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.
+
+:::
+
+:::warning{title="❓ Why not use Json Schema"}
+
+[`Json Schema`](https://json-schema.org/) 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**](/materials/introduction) to lower the barrier to entry.
+
+:::
+
+:::tip
+
+The variable engine provides `ASTFactory` for **type-safe** creation of `ASTNodeJSON`:
+
+```tsx
+import { ASTFactory } from '@flowgram/editor';
+
+/**
+ * Type-safely create a VariableDeclaration ASTNodeJSON
+ *
+ * Equivalent to:
+ * {
+ *   kind: 'VariableDeclaration',
+ *   key: 'var_index',
+ *   type: { kind: 'StringType' },
+ * }
+ */
+ASTFactory.createVariableDeclaration({
+  key: 'var_index',
+  type: { kind: 'StringType' },
+});
+```
+:::
+
+
+
+
+### Declaration
+
+Declaration = Identifier (Key) + Definition. In design mode, a declaration is an `ASTNode` that stores an identifier and its definition.
+
+- Identifier (Key): The index for accessing the declaration.
+- Definition: The information defined by the declaration. For example, a variable's definition = type + right-hand value.
+
+
+:::info{title="Example: Declarations in JavaScript"}
+
+**Variable Declaration** = Identifier + Variable Definition (Type + Initial Value)
+
+```javascript
+/**
+ * Identifier: some_var
+ * Variable Definition: type is number, initial value is 10
+ */
+const some_var: number = 10;
+```
+
+**Function Declaration** = Identifier + Function Definition (Function Parameters and Return Value + Function Body Implementation)
+
+```javascript
+/**
+ * Identifier: add
+ * Function Definition: parameters are two number variables a, b, and the return value is a number variable
+ */
+function add(a: number, b: number): number {
+  return a + b;
+}
+```
+
+**Struct Declaration** = Identifier + Struct Definition (Fields + Types)
+
+```javascript
+/**
+ * Identifier: Point
+ * Struct Definition: fields are x, y, both of type number
+ */
+interface Point {
+  x: number;
+  y: number;
+}
+```
+
+:::
+
+
+:::tip{title="The Role of Identifiers"}
+
+- The `Identifier` is the **index** of a declaration, used to access the `Definition` within the declaration.
+- Example: During compilation, a programming language uses the `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`).
+
+- Variable Field Declaration (`BaseVariableField`) = Identifier + Variable Field Definition (Type + Metadata + Initial Value)
+- Variable Declaration (`VariableDeclaration`) = **Globally Unique** Identifier + Variable Definition (Type + Metadata + Initial Value + Order within Scope)
+- Property Declaration (`Property`) = **Unique within Object** Identifier + Property Definition (Type + Metadata + Initial Value)
+
+
+
+
+
+### Type
+
+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`: string
+- `IntegerType`: integer
+- `NumberType`: floating-point number
+- `BooleanType`: boolean
+- `ObjectType`: 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.
+
+### Expression
+
+An expression takes **0 or more variables as input**, computes them in a **specific way**, and returns a new **variable**.
+
+```mermaid
+graph LR
+
+Input_Variable_1 --input--> Expression
+Input_Variable_2 --input--> Expression
+
+Expression --returns--> Output_Variable
+
+style Expression stroke:#333,stroke-width:3px;
+```
+
+In **design mode**, an expression is an `ASTNode`. In modeling, we only need to focus on:
+
+- Which variable declarations does the expression **use**?
+- How is the expression's **return type** inferred?
+
+```mermaid
+graph LR
+
+Variable_Declaration_1 --input--> Expression_in_Design_Mode
+Variable_Declaration_2 --input--> Expression_in_Design_Mode
+Expression_in_Design_Mode --infers--> Return_Type
+
+style Expression_in_Design_Mode stroke:#333,stroke-width:3px;
+
+```
+
+
+:::info{title="Example: Expression Inference in Design Mode"}
+
+Suppose we have an expression described in JavaScript code as `ref_var + 1`.
+
+Which variable declarations does the expression **use**?
+- The variable declaration corresponding to the `ref_var` identifier.
+
+How is the expression's **return type** inferred?
+- If the type of `ref_var` is `IntegerType`, the return type of `ref_var + 1` is `IntegerType`.
+- If the type of `ref_var` is `NumberType`, the return type of `ref_var + 1` is `NumberType`.
+- If the type of `ref_var` is `StringType`, the return type of `ref_var + 1` is `StringType`.
+
+:::
+
+:::info{title="Example: How the Variable Engine Implements Type Inference + Linking"}
+
+<div style={{  }}>
+  <div style={{ width: 500 }}>
+     <img loading="lazy" src="/variable/variable-batch-auto-infer.gif" alt="Automatic Type Inference" style={{ width: "100%"}} />
+  </div>
+
+  <div style={{ minWidth: 500 }}>
+
+
+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:
+
+```tsx
+ASTFactory.createVariableDeclaration({
+  key: 'item',
+  initializer: ASTFactory.createEnumerateExpression({
+    enumerateFor: ASTFactory.createKeyPathExpression({
+      keyPath: ['start_0', 'arr']
+    })
+  })
+})
+```
+
+The type inference chain is as follows:
+
+```mermaid
+graph LR
+
+  Array_String["Array&lt;String&gt;"]
+  Ref_Var["Variable with type
+ Array&lt;String&gt;"]
+
+  VariableDeclaration --initializer--> EnumerateExpression
+  KeyPathExpression --references--> Ref_Var
+  KeyPathExpression --return type--> Array_String
+  EnumerateExpression --iterates over--> KeyPathExpression
+  EnumerateExpression --return type--> String
+  VariableDeclaration -.inferred type.-> String
+  Array_String -.extracts subtype via iteration.-> String
+  Ref_Var -.type.-> Array_String
+
+```
+  </div>
+</div>
+
+
+
+
+:::
+
+
+### Scope Chain
+
+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
+
+`Dependency Scope` = Which scopes' output variables a scope can access.
+
+You can access a scope's `Dependency Scope` via `scope.depScopes`.
+
+
+#### Covering Scope
+
+`Covering Scope` = Which scopes can access the output variables of a scope.
+
+You can access a scope's `Covering Scope` via `scope.coverScopes`.
+
+
+## Variables in the Canvas
+
+FlowGram defines the following special types of scopes in the canvas:
+
+### Node Scope
+
+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:
+
+```mermaid
+graph BT
+
+  subgraph Current_Node
+    Child_Node_1.scope
+    Child_Node_2.scope
+    Current_Node.scope
+  end
+
+  Child_Node_1.scope -.depends on.-> Upstream_Node.scope
+  Child_Node_2.scope -.depends on.-> Upstream_Node.scope
+  Current_Node.scope -.depends on.-> Upstream_Node.scope
+  Downstream_Node.scope -.depends on.-> Upstream_Node.scope
+  Downstream_Node.scope -.depends on.-> Current_Node.scope
+
+  Downstream_Node.scope -.-|"<font color=red>❌ Not accessible</font>"| Child_Node_1.scope
+  Downstream_Node.scope -.-|"<font color=red>❌ Not accessible</font>"| Child_Node_2.scope
+
+  linkStyle 5 stroke:red,stroke-width:2px
+  linkStyle 6 stroke:red,stroke-width:2px
+
+
+  style Current_Node.scope fill:#f9f,stroke:#333,stroke-width:3px
+```
+
+:::warning
+
+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**.
+
+:::
+
+
+### Node Private Scope
+
+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:
+
+```mermaid
+graph BT
+  subgraph Current_Node
+    Child_Node_1.scope -.depends on.-> Current_Node.privateScope
+    Current_Node.scope -.depends on.-> Current_Node.privateScope
+    Child_Node_2.scope -.depends on.-> Current_Node.privateScope
+  end
+
+  Current_Node -.all depend on.-> Upstream_Node.scope
+  Downstream_Node.scope -.depends on.-> Current_Node.scope
+  Downstream_Node.scope -.depends on.-> Upstream_Node.scope
+
+
+  style Current_Node.privateScope fill:#f9f,stroke:#333,stroke-width:3px
+  style Current_Node.scope stroke:#333,stroke-width:3px
+```
+
+
+### Global Scope
+
+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](./variable-output#output-global-variables). Its scope chain relationship is shown in the figure below:
+
+```mermaid
+graph RL
+
+ subgraph Current_Node
+    Child_Node_1.scope
+    Child_Node_2.scope
+    Current_Node.scope
+    Current_Node.privateScope
+  end
+
+  Current_Node.scope -.depends on.-> Global_Scope
+  Upstream_Node.scope -.depends on.-> Global_Scope
+  Downstream_Node.scope -.depends on.-> Global_Scope
+  Current_Node.privateScope -.depends on.-> Global_Scope
+  Child_Node_1.scope -.depends on.-> Global_Scope
+  Child_Node_2.scope -.depends on.-> Global_Scope
+
+  style Current_Node.scope stroke:#333,stroke-width:3px
+  style Global_Scope fill:#f9f,stroke:#333,stroke-width:3px
+
+```
+
+
+
+## Overall Architecture
+
+![Architecture Diagram](/variable/concept/arch-en.png)
+
+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:
+
+### Variable Abstraction Layer
+
+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`.
+
+### Variable Implementation Layer
+
+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 `ASTNode`s or override existing `ASTNode`s through dependency injection for customization.
+
+### Variable Material Layer
+
+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.
+
+- For the use of variable materials, see: [Materials](/materials/introduction)

+ 21 - 17
apps/docs/src/en/guide/variable/variable-consume.mdx

@@ -1,10 +1,16 @@
-# Consuming Variables
+---
+description: Learn how to consume variables from the variable engine in FlowGram
+---
 
 
-In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications.
+# Consume Variables
+
+In FlowGram, when a node wants to use variables from a preceding node, it needs to consume them.
 
 
 ## Official Material: `VariableSelector`
 ## Official Material: `VariableSelector`
 
 
-To make it easier for you to integrate variable selection functionality into your application, the official materials provide the `VariableSelector` component. For details, please read the documentation: [VariableSelector](/materials/components/variable-selector)
+To make it easier for you to integrate variable selection functionality into your application, the official materials provide the `VariableSelector` component.
+
+For details, please read the documentation: [VariableSelector](/materials/components/variable-selector)
 
 
 
 
 ## Getting the Variable Tree within a Node
 ## Getting the Variable Tree within a Node
@@ -15,14 +21,13 @@ In the nodes on the canvas, we often need to get the variables available in the
 
 
 `useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope.
 `useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope.
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {7}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   useAvailableVariables,
   useAvailableVariables,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 // .... In a React component or Hook
 // .... In a React component or Hook
-
 const availableVariables = useAvailableVariables();
 const availableVariables = useAvailableVariables();
 
 
 const renderVariable = (variable: BaseVariableField) => {
 const renderVariable = (variable: BaseVariableField) => {
@@ -39,7 +44,7 @@ return availableVariables.map(renderVariable);
 
 
 When a variable's type is `Object`, we often need to "drill down" into it to get its properties. The `ASTMatch.isObject` method can help us determine if a variable's type is an object. If it is, we can recursively render its `properties`.
 When a variable's type is `Object`, we often need to "drill down" into it to get its properties. The `ASTMatch.isObject` method can help us determine if a variable's type is an object. If it is, we can recursively render its `properties`.
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {12}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   ASTMatch,
   ASTMatch,
@@ -62,7 +67,7 @@ const renderVariable = (variable: BaseVariableField) => ({
 
 
 Similar to the `Object` type, when we encounter an `Array` type variable, we also want to display its internal structure. For arrays, we are usually concerned with the type of its elements. `ASTMatch.isArray` can determine if a variable's type is an array. It's worth noting that the element type of an array can be anything, even another array. Therefore, we need a recursive helper function `getTypeChildren` to handle this situation.
 Similar to the `Object` type, when we encounter an `Array` type variable, we also want to display its internal structure. For arrays, we are usually concerned with the type of its elements. `ASTMatch.isArray` can determine if a variable's type is an array. It's worth noting that the element type of an array can be anything, even another array. Therefore, we need a recursive helper function `getTypeChildren` to handle this situation.
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {13,16}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   type BaseType,
   type BaseType,
@@ -116,12 +121,11 @@ const available = useScopeAvailable();
 console.log(available.variables);
 console.log(available.variables);
 ```
 ```
 
 
-
 ### Getting the Variable List
 ### Getting the Variable List
 
 
 The most basic usage is to get all the available variables in the current scope.
 The most basic usage is to get all the available variables in the current scope.
 
 
-```tsx
+```tsx {4,7}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 
 
 function MyComponent() {
 function MyComponent() {
@@ -146,7 +150,7 @@ When you are only concerned with the changes of a specific variable (especially
 
 
 Suppose we have an Object type variable named `user` with a `name` property. We want to update the component when `user.name` changes.
 Suppose we have an Object type variable named `user` with a `name` property. We want to update the component when `user.name` changes.
 
 
-```tsx
+```tsx {13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 
 
@@ -184,14 +188,14 @@ Let's use a table to compare these three core listening APIs in detail:
 | API | Trigger | Callback Parameters | Core Differences and Applicable Scenarios |
 | API | Trigger | Callback Parameters | Core Differences and Applicable Scenarios |
 | :--- | :--- | :--- | :--- |
 | :--- | :--- | :--- | :--- |
 | `onVariableListChange` | When the **list structure** of available variables changes. | `(variables: VariableDeclaration[]) => void` | **Only cares about the list itself**. For example, an upstream node adds/deletes an output variable, causing the total number or members of available variables to change. It does not care about internal or drilled-down changes to variables. Suitable for scenarios where the UI needs to be updated based on the presence or number of variables in the list. |
 | `onVariableListChange` | When the **list structure** of available variables changes. | `(variables: VariableDeclaration[]) => void` | **Only cares about the list itself**. For example, an upstream node adds/deletes an output variable, causing the total number or members of available variables to change. It does not care about internal or drilled-down changes to variables. Suitable for scenarios where the UI needs to be updated based on the presence or number of variables in the list. |
-| `onAnyVariableChange` | When the **content (meta, type, drilldown fields)** of **any** variable in the list changes. | `(changedVariable: VariableDeclaration) => void` | **Only cares about content (type, meta, drilldown) updates to variables**. For example, a user modifies the type of an output variable. It does not care about changes to the list structure. Suitable for scenarios where you need to react to changes in the content of any variable. |
-| `onListOrAnyVarChange` | When **either** of the above two situations occurs. | `(variables: VariableDeclaration[]) => void` | **The most comprehensive listener**, a combination of the previous two. It is triggered by either a change in the list structure or a change in the value of any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes. |
+| `onAnyVariableChange` | When the **type, metadata, and drilldown fields** of **any** variable in the list change. | `(changedVariable: VariableDeclaration) => void` | **Only cares about content updates**. For example, a user modifies the type of an output variable. It does not care about changes to the list structure. Suitable for scenarios where you need to react to changes in the content of any variable. |
+| `onListOrAnyVarChange` | When **either** of the above two situations occurs. | `(variables: VariableDeclaration[]) => void` | **The most comprehensive listener**, a combination of the previous two. It is triggered by either a change in the list structure or a change in any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes. |
 
 
 #### Code Example
 #### Code Example
 
 
 Let's look at a specific example of how to use these APIs in a component.
 Let's look at a specific example of how to use these APIs in a component.
 
 
-```tsx
+```tsx {9-11,14-16,19-22}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
 
 
@@ -204,14 +208,14 @@ function AdvancedListenerComponent() {
       console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
       console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
     });
     });
 
 
-    // 2. Listen for changes in the value of any variable
+    // 2. Listen for changes in any variable
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
-      console.log(`The content of variable '${changedVariable.keyPath.join('.')}' has changed!`);
+      console.log(`The type, drilldown, or meta of variable '${changedVariable.keyPath.join('.')}' has changed`);
     });
     });
 
 
-    // 3. Listen for all changes (structure or content)
+    // 3. Listen for all changes (structure or individual variable content)
     const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
     const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
-      console.log('The variable list or the content of one of its variables has changed!');
+      console.log('The variable list or one of its variables has changed!');
       // Note: The callback parameter here is the complete variable list, not the single changed variable
       // Note: The callback parameter here is the complete variable list, not the single changed variable
     });
     });
 
 

+ 45 - 57
apps/docs/src/en/guide/variable/variable-output.mdx

@@ -1,4 +1,8 @@
-# Outputting Variables
+---
+description: Learn how to use the variable engine to output variables in FlowGram
+---
+
+# Output Variables
 
 
 We mainly divide output variables into three categories:
 We mainly divide output variables into three categories:
 
 
@@ -14,15 +18,15 @@ We usually have three ways to output node variables:
 
 
 ### Method 1: Sync via Form Side Effects
 ### Method 1: Sync via Form Side Effects
 
 
-[Form Side Effects](/guide/form/form#副作用-effect) are usually configured in the node's `form-meta.ts` file and are the most common way to define node output variables.
+[Form Side Effects](/guide/form/form#side-effects-effect) are usually configured in the node's `form-meta.ts` file and are the most common way to define node output variables.
 
 
 #### `provideJsonSchemaOutputs` Material
 #### `provideJsonSchemaOutputs` Material
 
 
 If the structure of the output variable required by the node matches the [JSON Schema](https://json-schema.org/) structure, you can use the `provideJsonSchemaOutputs` side effect (Effect) material.
 If the structure of the output variable required by the node matches the [JSON Schema](https://json-schema.org/) structure, you can use the `provideJsonSchemaOutputs` side effect (Effect) material.
 
 
-It is very simple to use, just add two lines of configuration in the `effect` of `formMeta`:
+Just add two lines of configuration in the `effect` of `formMeta`:
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {8-9}
 import {
 import {
   syncVariableTitle,
   syncVariableTitle,
   provideJsonSchemaOutputs,
   provideJsonSchemaOutputs,
@@ -36,22 +40,23 @@ export const formMeta = {
 };
 };
 ```
 ```
 
 
-#### Via `createEffectFromVariableProvider`
+#### Customizing Output with `createEffectFromVariableProvider`
+
 
 
 `provideJsonSchemaOutputs` only adapts to `JsonSchema`. If you want to define your own set of Schema, then you need to customize the side effects of the form.
 `provideJsonSchemaOutputs` only adapts to `JsonSchema`. If you want to define your own set of Schema, then you need to customize the side effects of the form.
 
 
 :::note
 :::note
 
 
-Flowgram provides `createEffectFromVariableProvider`, you only need to define a `parse` function to customize your own variable synchronization side effects:
+FlowGram provides `createEffectFromVariableProvider`, you only need to define a `parse` function to customize your own variable synchronization side effects:
 - `parse` will be called when the form value is initialized and updated
 - `parse` will be called when the form value is initialized and updated
 - The input of `parse` is the value of the current field's form
 - The input of `parse` is the value of the current field's form
 - The output of `parse` is the variable AST information
 - The output of `parse` is the variable AST information
 
 
 :::
 :::
 
 
-In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`:
+In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2` respectively:
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {27-38,41-52}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -81,13 +86,13 @@ export const formMeta =  {
     'path.to.value': createEffectFromVariableProvider({
     'path.to.value': createEffectFromVariableProvider({
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
             title: `Your Output Variable Title`,
             title: `Your Output Variable Title`,
           },
           },
           key: `uid_${node.id}`,
           key: `uid_${node.id}`,
           type: createTypeFromValue(v)
           type: createTypeFromValue(v)
-        }
+        }]
       }
       }
     }),
     }),
     // Create second variable
     // Create second variable
@@ -95,13 +100,13 @@ export const formMeta =  {
     'path.to.value2': createEffectFromVariableProvider({
     'path.to.value2': createEffectFromVariableProvider({
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
             title: `Your Output Variable Title 2`,
             title: `Your Output Variable Title 2`,
           },
           },
           key: `uid_${node.id}_2`,
           key: `uid_${node.id}_2`,
           type: createTypeFromValue(v)
           type: createTypeFromValue(v)
-        }
+        }]
       }
       }
     }),
     }),
   },
   },
@@ -115,7 +120,8 @@ export const formMeta =  {
 
 
 If multiple fields are synchronized to one variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize the variable data of multiple fields to the same namespace.
 If multiple fields are synchronized to one variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize the variable data of multiple fields to the same namespace.
 
 
-```tsx pure title="form-meta.ts"
+
+```tsx pure title="form-meta.ts" {11}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -130,16 +136,13 @@ const variableSyncEffect = createEffectFromVariableProvider({
 
 
   // Parse the form value into a variable
   // Parse the form value into a variable
   parse(_, { form, node }) {
   parse(_, { form, node }) {
-    return {
+    return [{
       meta: {
       meta: {
-        title: `Your Output Variable Title`,
+        title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
       },
       },
       key: `uid_${node.id}`,
       key: `uid_${node.id}`,
-      type: createTypeFromValue({
-        value1: form.getValueIn('path.to.value'),
-        value2: form.getValueIn('path.to.value2'),
-      })
-    }
+      type: ASTFactory.createCustomType({ typeName: "CustomVariableType" })
+    }]
   }
   }
 })
 })
 
 
@@ -172,7 +175,7 @@ If `createEffectFromVariableProvider` does not meet your needs, you can also dir
 :::
 :::
 
 
 
 
-```tsx pure title="form-meta.tsx"
+```tsx pure title="form-meta.tsx" {10-18,29-38}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
 
 
 export const formMeta = {
 export const formMeta = {
@@ -185,10 +188,10 @@ export const formMeta = {
         context.node.scope.setVar(
         context.node.scope.setVar(
           ASTFactory.createVariableDeclaration({
           ASTFactory.createVariableDeclaration({
             meta: {
             meta: {
-              title: `Your Output Variable Title`,
+              title: `Title_${value}`,
             },
             },
             key: `uid_${node.id}`,
             key: `uid_${node.id}`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createString(),
           })
           })
         )
         )
 
 
@@ -205,10 +208,10 @@ export const formMeta = {
           'namespace_2',
           'namespace_2',
           ASTFactory.createVariableDeclaration({
           ASTFactory.createVariableDeclaration({
             meta: {
             meta: {
-              title: `Your Output Variable Title 2`,
+              title: `Title_${value}`,
             },
             },
             key: `uid_${node.id}_2`,
             key: `uid_${node.id}_2`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createNumber(),
           })
           })
         )
         )
 
 
@@ -233,7 +236,7 @@ In addition to static configuration in the form, we can also freely and dynamica
 
 
 The following example demonstrates how to get the `Scope` of the start node in the `onInit` life cycle of the plugin and perform a series of operations on its variables.
 The following example demonstrates how to get the `Scope` of the start node in the `onInit` life cycle of the plugin and perform a series of operations on its variables.
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {10-22}
 import {
 import {
   FlowDocument,
   FlowDocument,
   definePluginCreator,
   definePluginCreator,
@@ -264,7 +267,7 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
 
 
 The following example demonstrates how to get the Scope of a newly created node through `onNodeCreate` and synchronize variables by listening to `node.form.onFormValuesChange`.
 The following example demonstrates how to get the Scope of a newly created node through `onNodeCreate` and synchronize variables by listening to `node.form.onFormValuesChange`.
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {10,29}
 import {
 import {
   FlowDocument,
   FlowDocument,
   definePluginCreator,
   definePluginCreator,
@@ -318,7 +321,9 @@ It breaks the principle of **separation of data and rendering**, leading to tigh
 
 
 :::
 :::
 
 
-```tsx pure title="form-meta.ts"
+The following example demonstrates how to synchronize and update variables through the `useCurrentScope` event in `formMeta.render`.
+
+```tsx pure title="form-meta.ts" {13}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -355,28 +360,14 @@ export const formMeta = {
 }
 }
 ```
 ```
 
 
-## Outputting Node Private Variables
 
 
-Private variables are variables that can only be accessed within the current node and its child nodes.
 
 
-Private variables can be set and obtained through the private scope `node.privateScope`. Its scope chain relationship is shown in the following figure:
 
 
-```mermaid
-graph BT
-  subgraph Current_Node
-    Child_Node_1.scope -.depends on.-> Current_Node.privateScope
-    Current_Node.scope -.depends on.-> Current_Node.privateScope
-    Child_Node_2.scope -.depends on.-> Current_Node.privateScope
-  end
 
 
-  Current_Node -.all depend on.-> Upstream_Node.scope
-  Downstream_Node.scope -.depends on.-> Current_Node.scope
-  Downstream_Node.scope -.depends on.-> Upstream_Node.scope
 
 
+## Outputting Node Private Variables
 
 
-  style Current_Node.privateScope fill:#f9f,stroke:#333,stroke-width:3px
-  style Current_Node.scope stroke:#333,stroke-width:3px
-```
+Private variables are variables that can only be accessed within the current node and its child nodes. (See also: [Node Private Scope](./concept#node-private-scope))
 
 
 Only two of the methods are listed below, and other methods can be deduced from the [Output Node Variables](#outputting-node-variables) method.
 Only two of the methods are listed below, and other methods can be deduced from the [Output Node Variables](#outputting-node-variables) method.
 
 
@@ -386,7 +377,7 @@ Only two of the methods are listed below, and other methods can be deduced from
 - When `scope` is set to `private`, the scope of the variable is the private scope of the current node `node.privateScope`
 - When `scope` is set to `private`, the scope of the variable is the private scope of the current node `node.privateScope`
 - When `scope` is set to `public`, the scope of the variable is the scope of the current node `node.scope`
 - When `scope` is set to `public`, the scope of the variable is the scope of the current node `node.scope`
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {11}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -400,13 +391,13 @@ export const formMeta =  {
       scope: 'private',
       scope: 'private',
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
-            title: `Your Private Variable Title`,
+            title: `Private_${v}`,
           },
           },
           key: `uid_${node.id}_locals`,
           key: `uid_${node.id}_locals`,
-          type: createTypeFromValue(v)
-        }
+          type: ASTFactory.createBoolean(),
+        }]
       }
       }
     }),
     }),
   },
   },
@@ -423,7 +414,7 @@ export const formMeta =  {
 The API of `node.privateScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
 The API of `node.privateScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
 
 
 
 
-```tsx pure title="form-meta.tsx"
+```tsx pure title="form-meta.tsx" {10-18}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
 
 
 export const formMeta = {
 export const formMeta = {
@@ -439,7 +430,7 @@ export const formMeta = {
               title: `Your Private Variable Title`,
               title: `Your Private Variable Title`,
             },
             },
             key: `uid_${node.id}`,
             key: `uid_${node.id}`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createInteger(),
           })
           })
         )
         )
 
 
@@ -468,14 +459,13 @@ Similar to node variables, we also have two main ways to obtain the scope of glo
 In the context of the plugin (`ctx`), we can directly "inject" the instance of `GlobalScope`:
 In the context of the plugin (`ctx`), we can directly "inject" the instance of `GlobalScope`:
 
 
 
 
-```tsx pure title="global-variable-plugin.tsx"
+```tsx pure title="global-variable-plugin.tsx" {10-20}
 import {
 import {
   GlobalScope,
   GlobalScope,
   definePluginCreator,
   definePluginCreator,
   PluginCreator
   PluginCreator
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
-
 export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
 export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
     onInit(ctx, options) {
     onInit(ctx, options) {
@@ -500,14 +490,13 @@ export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions
 
 
 If you want to interact with global variables in the React component of the canvas, you can use the `useService` Hook to get the instance of `GlobalScope`:
 If you want to interact with global variables in the React component of the canvas, you can use the `useService` Hook to get the instance of `GlobalScope`:
 
 
-```tsx pure title="global-variable-component.tsx"
+```tsx pure title="global-variable-component.tsx" {7}
 import {
 import {
   GlobalScope,
   GlobalScope,
   useService,
   useService,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 function GlobalVariableComponent() {
 function GlobalVariableComponent() {
-
   const globalScope = useService(GlobalScope)
   const globalScope = useService(GlobalScope)
 
 
   // ...
   // ...
@@ -537,7 +526,7 @@ The API of `GlobalScope` is designed to be almost identical to the node scope (`
 
 
 Here is a comprehensive example of operating global variables in a plugin:
 Here is a comprehensive example of operating global variables in a plugin:
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {11-39}
 import {
 import {
   GlobalScope,
   GlobalScope,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
@@ -562,7 +551,6 @@ onInit(ctx, options) {
 
 
   globalScope.clearVar()
   globalScope.clearVar()
 
 
-
   // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
   // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
     globalScope.setVar(
     globalScope.setVar(
       'namespace_1',
       'namespace_1',

BIN
apps/docs/src/public/variable/concept/arch-en.png


BIN
apps/docs/src/public/variable/concept/arch-zh.png


BIN
apps/docs/src/public/variable/concept/concepts-en.png


BIN
apps/docs/src/public/variable/concept/concepts-zh.png


BIN
apps/docs/src/public/variable/concept/scope-1.png


BIN
apps/docs/src/public/variable/concept/scope-2.png


BIN
apps/docs/src/public/variable/concept/scope-3.png


+ 0 - 1
apps/docs/src/zh/guide/concepts/_meta.json

@@ -1,7 +1,6 @@
 [
 [
   "canvas-engine",
   "canvas-engine",
   "node-engine",
   "node-engine",
-  "variable-engine",
   "ecs",
   "ecs",
   "ioc",
   "ioc",
   "reactflow"
   "reactflow"

+ 0 - 86
apps/docs/src/zh/guide/concepts/variable-engine.mdx

@@ -1,86 +0,0 @@
-# 变量引擎
-
-## 整体设计
-
-### 架构分层
-
-:::warning 架构分层
-变量引擎设计上遵循 DIP(依赖反转)原则,按照 代码稳定性、抽象层次 以及和 业务的远近 分为三层:
-- 变量抽象层:变量架构中抽象层次最高,代码也最为稳定的部分
-- 变量实现层:变量架构中变动较大,不同业务之间通常存在调整的部分
-- 变量业务层:变量架构中提供给业务的 Facade ,与画布引擎、节点引擎联动的部分
-
-:::
-
-![架构分层图](@/public/variable-engine.png)
-
-
-### 术语表
-
-#### 🌟 作用域(Scope)
-:::warning ⭐️⭐️⭐️ 定义:
-一种约定的空间,空间内 通过 AST 来描述变量声明和消费情况
-- 约定的空间:空间是什么,完全由业务定义
-- 在低代码设计态中,可以是一个节点、一个组件、一个右侧面板...
-- 在一段代码中,可以是一行 Statement、一段代码块、一个函数、一个文件...
-
-:::
-
-作用域的空间是什么?可以由不同的业务来划定。
-
-
-#### 🌟 抽象语法树(AST)
-
-:::warning 定义:
-⭐️⭐️⭐️ 一种协议,通过树的形式,组合 AST 节点,实现对变量信息的显式/隐式 CRUD
-- AST 节点:AST 中可响应式的协议节点
-- 显式 CRUD,如:业务显示设定一个变量的变量类型
-- 隐式 CRUD,如:业务声明一个变量,变量会根据其初始化参数自动推导变量类型
-
-:::
-
-:::warning 作用域里面的变量、类型、表达式、结构体 等等变量信息... 本质上都是 AST 节点的组合
-- 变量 -> VariableDeclaration 节点
-- 表达式 -> Expression 节点
-- 类型 -> TypeNode 节点
-- 结构体 -> StructDeclaration 节点
-
-:::
-
-参考链接:https://ts-ast-viewer.com/
-
-#### 变量(Variable)
-
-:::warning 定义:
-一种用于声明新变量的 AST 节点,通过唯一标识符 指向一个 在特定集合范围内变动的值
-- 在特定集合范围内变动的值:变量的值必须在 变量类型 描述的范围内
-- 唯一标识符:变量必须有一个唯一的 Key 值
-
-:::
-
-![JavaScript中的变量,唯一 Key + 指向一个变动的值](@/public/variable-code.png)
-
-#### 变量类型(Variable Type)
-
-:::warning 定义:
-⭐️⭐️⭐️ 一种 AST 节点,用于约束一个变量,被约束的变量值只能在预先设定的集合范围内变动
-- 一个变量可以绑定一个变量类型
-
-:::
-<table>
-  <tr>
-    <td><img loading="lazy" src="/variable-type1.png"/></td>
-    <td><img loading="lazy" src="/variable-type2.png"/></td>
-  </tr>
-</table>
-
-### 变量引擎的形象理解
-
-:::warning 想像这样一个变量引擎的世界:
-- 通过一个个 作用域 来划定出一个个 国家
-- 每个国家包含三大公民:声明、类型、表达式
-- 国家与国家之间通过 作用域链 来实现交流
-
-:::
-
-![图解](@/public/varaible-zone.png)

+ 1 - 0
apps/docs/src/zh/guide/variable/_meta.json

@@ -1,5 +1,6 @@
 [
 [
   "basic",
   "basic",
+  "concept",
   "variable-output",
   "variable-output",
   "variable-consume"
   "variable-consume"
 ]
 ]

+ 19 - 10
apps/docs/src/zh/guide/variable/basic.mdx

@@ -1,4 +1,15 @@
-# 变量基础
+---
+description: 介绍什么是变量,以及变量引擎的作用
+---
+
+
+# 介绍
+
+:::warning
+
+本文档的变量引擎属于 FlowGram **设计态**,和运行态的变量引擎不同。
+
+:::
 
 
 ## 什么是变量?
 ## 什么是变量?
 
 
@@ -9,7 +20,7 @@
 一个变量通常由三部分组成:
 一个变量通常由三部分组成:
 
 
 - **名字(唯一标识符)**:就像你的名字一样,它让大家能准确地找到这个变量。例如 `userName`、`orderId`。
 - **名字(唯一标识符)**:就像你的名字一样,它让大家能准确地找到这个变量。例如 `userName`、`orderId`。
-- **值**:容器里装的东西。它可以是数字 `123`,文字 `"Hello Flowgram!"`,或者一个开关状态 `true` / `false`。
+- **值**:容器里装的东西。它可以是数字 `123`,文字 `"Hello FlowGram!"`,或者一个开关状态 `true` / `false`。
 - **类型**:规定了这个容器能装哪种东西。比如,有的只能装数字,有的只能装文字。
 - **类型**:规定了这个容器能装哪种东西。比如,有的只能装数字,有的只能装文字。
 
 
 ---
 ---
@@ -17,7 +28,7 @@
 举个例子,在一个“智能问答”流程中:
 举个例子,在一个“智能问答”流程中:
 
 
 <div style={{display: 'flex', gap: '20px'}}>
 <div style={{display: 'flex', gap: '20px'}}>
-  <img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
+  <img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" alt="智能问答流程" />
   <div>
   <div>
     <p style={{marginTop: 10}}>1. **`WebSearch` 节点**:负责上网搜索,然后把搜到的知识(比如“今天天气怎么样?”的答案)放进一个名为 `natural_language_desc` 的变量里。</p>
     <p style={{marginTop: 10}}>1. **`WebSearch` 节点**:负责上网搜索,然后把搜到的知识(比如“今天天气怎么样?”的答案)放进一个名为 `natural_language_desc` 的变量里。</p>
     <p style={{marginTop: 5}}>2. **`LLM` 节点**:它会接过 `natural_language_desc` 这个“信使”,读取里面的内容,然后用更自然、更友好的方式回答用户。</p>
     <p style={{marginTop: 5}}>2. **`LLM` 节点**:它会接过 `natural_language_desc` 这个“信使”,读取里面的内容,然后用更自然、更友好的方式回答用户。</p>
@@ -29,7 +40,7 @@
 
 
 随着工作流复杂度的提升,变量的数量和管理难度也随之增加。
 随着工作流复杂度的提升,变量的数量和管理难度也随之增加。
 
 
-为了应对这一挑战,Flowgram 提供了强大的**变量引擎**。
+为了应对这一挑战,FlowGram 提供了强大的**变量引擎**。
 
 
 它如同一位专业的“数据管家”,能够系统化地管理所有变量,确保数据流的清晰与稳定。
 它如同一位专业的“数据管家”,能够系统化地管理所有变量,确保数据流的清晰与稳定。
 
 
@@ -41,11 +52,11 @@
     <p className="rs-tip">变量引擎能够精确控制每个变量的有效范围(即作用域)。如同为不同房间配置专属钥匙,它确保了变量只在预期的节点中被访问,从而有效避免了数据污染和意外的逻辑错误。</p>
     <p className="rs-tip">变量引擎能够精确控制每个变量的有效范围(即作用域)。如同为不同房间配置专属钥匙,它确保了变量只在预期的节点中被访问,从而有效避免了数据污染和意外的逻辑错误。</p>
     <div style={{display: "flex", gap: "25px"}}>
     <div style={{display: "flex", gap: "25px"}}>
       <div>
       <div>
-        <img loading="lazy" src="/variable/variable-scope-feature-1.png" />
+        <img loading="lazy" src="/variable/variable-scope-feature-1.png" alt="作用域约束示例 1" />
         <p style={{marginTop: '10px'}}>`Start` 节点定义的 `query` 变量,在它后面的 `LLM` 和 `End` 节点都能轻松访问。</p>
         <p style={{marginTop: '10px'}}>`Start` 节点定义的 `query` 变量,在它后面的 `LLM` 和 `End` 节点都能轻松访问。</p>
       </div>
       </div>
       <div>
       <div>
-        <img loading="lazy" src="/variable/variable-scope-feature-2.png" />
+        <img loading="lazy" src="/variable/variable-scope-feature-2.png" alt="作用域约束示例 2" />
         <p style={{marginTop: '10px'}}>`LLM` 节点在一个 `Condition` 分支里,像是在一个独立的房间。外面的 `End` 节点自然就拿不到它的 `result` 变量了。</p>
         <p style={{marginTop: '10px'}}>`LLM` 节点在一个 `Condition` 分支里,像是在一个独立的房间。外面的 `End` 节点自然就拿不到它的 `result` 变量了。</p>
       </div>
       </div>
     </div>
     </div>
@@ -53,13 +64,13 @@
   <div>
   <div>
     <b>变量结构透视:轻松洞悉复杂数据</b>
     <b>变量结构透视:轻松洞悉复杂数据</b>
     <p className="rs-tip">当变量变得复杂时(例如一个包含许多层级的对象),变量引擎能让你像剥洋葱一样,一层层地深入探索它的内部结构,所有细节都尽在掌握。</p>
     <p className="rs-tip">当变量变得复杂时(例如一个包含许多层级的对象),变量引擎能让你像剥洋葱一样,一层层地深入探索它的内部结构,所有细节都尽在掌握。</p>
-    <img loading="lazy" src="/variable/variable-tree-management.gif" />
+    <img loading="lazy" src="/variable/variable-tree-management.gif" alt="变量结构透视" />
     <p style={{marginTop: '10px'}}>这张图里,你能看到所有节点的输出变量,以及它们之间的层级关系,像一棵枝繁叶茂的树。</p>
     <p style={{marginTop: '10px'}}>这张图里,你能看到所有节点的输出变量,以及它们之间的层级关系,像一棵枝繁叶茂的树。</p>
   </div>
   </div>
   <div>
   <div>
     <b>类型自动推导:心有灵犀一点通</b>
     <b>类型自动推导:心有灵犀一点通</b>
     <p className="rs-tip">你不用再挨个告诉每个变量它应该是什么类型,变量引擎会像你的“灵魂伴侣”一样,根据上下文自动推导出它的类型。</p>
     <p className="rs-tip">你不用再挨个告诉每个变量它应该是什么类型,变量引擎会像你的“灵魂伴侣”一样,根据上下文自动推导出它的类型。</p>
-    <img loading="lazy" src="/variable/variable-batch-auto-infer.gif" />
+    <img loading="lazy" src="/variable/variable-batch-auto-infer.gif" alt="类型自动推导" />
     <p style={{marginTop: '10px'}}>例如,当 `Start` 节点中 `arr` 变量的类型发生变更时,`Batch` 节点输出的 `item` 类型也会自动同步更新,确保了类型的一致性。</p>
     <p style={{marginTop: '10px'}}>例如,当 `Start` 节点中 `arr` 变量的类型发生变更时,`Batch` 节点输出的 `item` 类型也会自动同步更新,确保了类型的一致性。</p>
   </div>
   </div>
 </div>
 </div>
@@ -80,5 +91,3 @@
 }
 }
 ```
 ```
 
 
-
-

+ 3 - 0
apps/docs/src/zh/guide/variable/cases/_meta.json

@@ -0,0 +1,3 @@
+[
+  "case-batch-variable"
+]

+ 5 - 0
apps/docs/src/zh/guide/variable/cases/case-batch-variable.mdx

@@ -0,0 +1,5 @@
+---
+description: 介绍如何实现批处理节点中的变量逻辑
+---
+
+# 批处理变量实现思路 (WIP)

+ 506 - 0
apps/docs/src/zh/guide/variable/concept.mdx

@@ -0,0 +1,506 @@
+---
+description: 介绍变量引擎的核心概念
+---
+
+# 概念
+
+
+:::tip
+
+变量引擎概念较多且抽象。本文通过 🌟 标记出了一批可以**优先理解**的概念。
+
+:::
+
+:::info{title="📖 术语快速查询"}
+
+- [**变量**](#变量) 🌟
+- [**作用域**](#作用域-) 🌟:一种容器,聚合了一系列变量信息,同时维护与其他作用域的依赖关系。
+- [**AST**](#ast-) 🌟:作用域通过 AST 存储变量信息。
+- [**ASTNode**](#astnode):存储变量信息的基本单元。
+- [**ASTNodeJSON**](#astnodejson):ASTNode 的 JSON 表示。
+- [**声明**](#声明) 🌟:标识符 + 定义。
+- [**类型**](#类型) 🌟:变量值的约束。
+- [**表达式**](#表达式):通过特定方法组合若干变量,返回一个新的变量。
+- [**作用域链**](#作用域链):定义一个作用域可以引用哪些作用域的变量。
+- [**依赖作用域**](#依赖作用域):作用域可以访问哪些作用域的输出变量。
+- [**覆盖作用域**](#覆盖作用域):作用域的输出变量可以被哪些作用域访问。
+- [**节点作用域**](#节点作用域) 🌟:可以访问上游节点的输出变量,其输出变量也可以被下游节点访问。
+- [**节点私有作用域**](#节点私有作用域):节点私有作用域只能被节点本身或者子节点访问。
+- [**全局作用域**](#全局作用域):所有节点的作用域都可以访问全局作用域的变量。
+
+:::
+
+
+
+## 核心概念
+
+变量引擎核心概念可以通过下图总结:
+
+<img src="/variable/concept/concepts-zh.png" alt="变量核心概念关系图" width="600" />
+
+
+### 变量
+
+详见 [变量介绍](./basic.mdx)
+
+:::warning{title="⚠️ 变量在设计和运行中的关注点不同"}
+
+**在流程设计中,变量只关注定义,不关注值**。变量的值在流程的[运行时](/guide/runtime/introduction)才会被动态计算。
+
+:::
+
+### 作用域 🌟
+
+作用域(Scope)是一种**容器**:容器内聚合了一系列**变量信息**,同时维护了**与其他作用域的依赖关系**。
+
+作用域的范围可以根据业务场景的不同约定:
+
+| 场景 | 示例 |
+| :--- | :--- |
+| 流程里节点可以约定为作用域 | <img src="/variable/concept/scope-1.png" alt="节点作用域" width="600" /> |
+| 全局变量侧边栏也可以约定为作用域 | <img src="/variable/concept/scope-2.png" alt="全局作用域" width="600" /> |
+| 界面编辑里组件(含变量)可以约定为作用域 | <img src="/variable/concept/scope-3.png" alt="组件作用域" width="600" /> |
+
+
+
+:::warning{title="为什么 FlowGram 要在节点之外,新抽象一个作用域的概念?"}
+
+1. 节点不等同于作用域
+2. 存在一些作用域(如:全局作用域)和节点无关
+3. 一个节点可以存在多个作用域(如:循环私有作用域)
+
+:::
+
+### AST 🌟
+
+作用域(Scope)通过 `AST` 存储变量信息。
+
+:::tip
+
+通过 `scope.ast` 可以访问作用域内的 `AST` 树,从而可以对变量信息进行 CRUD 操作。
+
+:::
+
+
+#### ASTNode
+
+`ASTNode` 是变量引擎中用于**存储变量信息**的**基本信息单元**。它可以为各种**变量信息建模**。这些变量信息包括:
+
+- **声明**:如 `VariableDeclaration` ,用于声明新变量。
+- **类型**:如 `StringType`,用于表示 String 类型。
+- **表达式**:如 `KeyPathExpression`,用于对变量的引用。
+
+:::info{title="ASTNode 具有以下特点"}
+
+- **树状结构**: `ASTNode` 可以嵌套形成树(`AST`),表示复杂的变量结构。
+- **序列化**: `ASTNode` 可以与 JSON 格式(`ASTNodeJSON`)相互转换,以便存储或传输。
+- **可扩展**: 可以通过扩展 `ASTNode` 基类来添加新功能。
+- **响应式**: `ASTNode` 值的变化会触发事件,从而实现响应式编程模式。
+
+:::
+
+#### ASTNodeJSON
+
+`ASTNodeJSON` 是 `ASTNode` 的**纯 JSON 序列化**表示。
+
+`ASTNodeJSON` 包含一个 `kind` 字段,用于表示 `ASTNode` 的类型:
+
+```tsx
+/**
+ * 相当于 JavaScript 代码:
+ * `var var_index: string`
+ */
+{
+  kind: 'VariableDeclaration',
+  key: 'var_index',
+  type: { kind: 'StringType' },
+}
+```
+
+用户在使用变量引擎时,通过 `ASTNodeJSON` 描述变量信息,然后通过变量引擎**实例化**为 `ASTNode`,并将其添加到作用域中。
+
+```tsx
+/**
+ * 通过 scope.setVar 方法,将 ASTNodeJSON 实例化为 ASTNode,并添加到作用域中
+ */
+const variableDeclaration: VariableDeclaration = scope.setVar({
+  kind: 'VariableDeclaration',
+  key: 'var_index',
+  type: { kind: 'StringType' },
+});
+
+/**
+ * ASTNodeJSON 实例化为 ASTNode 之后,可以进行响应式监听
+ */
+variableDeclaration.onTypeChange((newType) => {
+  console.log('变量类型变化了', newType);
+})
+
+```
+
+:::info{title="概念比对"}
+
+`ASTNodeJSON` 和 `ASTNode` 的关系,类似于 React 中 `JSX` 和 `VDOM` 的关系
+- `ASTNodeJSON` 通过变量引擎实例化为 `ASTNode`
+- `JSX` 通过 React 引擎实例化为 `VDOM`
+
+:::
+
+:::warning{title="❓ 为什么不用 Json Schema"}
+
+[`Json Schema`](https://json-schema.org/) 是一种用于描述 JSON 数据结构的格式:
+
+- `Json Schema` 只描述了变量的类型信息,而 `ASTNodeJSON` 还可以包含变量的其他信息(如:变量的初始值)。
+- `ASTNodeJSON` 可以通过变量引擎实例化为 `ASTNode`,从而实现响应式监听等能力。
+- `Json Schema` 擅长描述 Json 的类型,而 `ASTNodeJSON` 可以通过自定义扩展定义行为更复杂的信息。
+
+在技术选型上,`变量引擎内核`需要更强大的扩展与表达能力,因此需要用 `ASTNodeJSON` 来描述更丰富更复杂的变量信息,如:通过定义变量的初始值,实现变量类型的动态推导 + 自动联动。
+
+不过 `Json Schema` 作为业界通用的 JSON 类型描述格式,在易用性、跨团队沟通以及生态(如 ajv、zod)上更有优势。因此我们在[**物料库**](/materials/introduction)中大量使用了 Json Schema,来降低大家的上手成本。
+
+:::
+
+:::tip
+
+变量引擎提供了 `ASTFactory`,可以**类型安全**地创建 `ASTNodeJSON`:
+
+```tsx
+import { ASTFactory } from '@flowgram/editor';
+
+/**
+ * 类型安全地创建 VariableDeclaration ASTNodeJSON
+ *
+ * 等价于:
+ * {
+ *   kind: 'VariableDeclaration',
+ *   key: 'var_index',
+ *   type: { kind: 'StringType' },
+ * }
+ */
+ASTFactory.createVariableDeclaration({
+  key: 'var_index',
+  type: { kind: 'StringType' },
+});
+```
+:::
+
+
+
+
+### 声明
+
+声明 = 标识符(Key) + 定义(Definition)。在设计态中,声明是一种存储标识符 + 变量信息的 `ASTNode`。
+
+- 标识符(Key):访问声明的索引。
+- 定义(Definition):声明定义的信息。如:变量的定义 = 类型 + 右值。
+
+
+:::info{title="举例:JavaScript 中的声明"}
+
+**变量声明** = 标识符 + 变量定义(类型 + 初始值)
+
+```javascript
+/**
+ * 标识符:some_var
+ * 变量定义:类型为 number,初始值为 10
+ */
+const some_var: number = 10;
+```
+
+**函数声明** = 标识符 + 函数定义(函数入参出参 + 函数体实现)
+
+```javascript
+/**
+ * 标识符:add
+ * 函数定义:入参为两个 number 类型的变量 a, b,出参为 number 类型的变量
+ */
+function add(a: number, b: number): number {
+  return a + b;
+}
+```
+
+**结构体声明** = 标识符 + 结构体定义(字段 + 类型)
+
+```javascript
+/**
+ * 标识符:Point
+ * 结构体定义:字段为 x, y,类型均为 number
+ */
+interface Point {
+  x: number;
+  y: number;
+}
+```
+
+:::
+
+
+:::tip{title="标识符的作用"}
+
+- `标识符`是声明的**索引**,用于访问声明中的`定义`。
+- 举例:编程语言在编译时,通过`标识符`找到变量的类型`定义`,从而可以进行类型检查。
+
+:::
+
+
+变量引擎目前只提供了**变量字段声明**(`BaseVariableField`),并基于此扩展了**变量声明**(`VariableDeclaration`)和**属性声明**(`Property`)两种声明。
+
+- 变量字段声明(`BaseVariableField`)= 标识符 + 变量字段定义(类型 + 元信息 + 初始值)
+- 变量声明(`VariableDeclaration`)= **全局唯一**标识符 + 变量定义(类型 + 元信息 + 初始值 + 作用域内排序)
+- 属性声明(`Property`)= **Object 内唯一**标识符 + 属性定义(类型 + 元信息 + 初始值)
+
+
+
+
+
+### 类型
+
+类型用于**约束变量值的范围**。在设计态中,类型是一种 `ASTNode`。
+
+变量引擎内置了 JSON 的**基础类型**:
+- `StringType`:字符串
+- `IntegerType`:整数
+- `NumberType`:浮点数
+- `BooleanType`:布尔值
+- `ObjectType`:对象,可下钻 `Property` 声明。
+- `ArrayType`:数组,可下钻其他类型。
+
+同时新增了:
+- `MapType`:键值对,键和值都可以进行类型定义。
+- `CustomType`:由用户进行自定义扩展,如日期、时间、文件类型等。
+
+### 表达式
+
+表达式**输入 0 个或者多个变量**,并通过**通过特定方式**进行计算,返回一个新的**变量**。
+
+```mermaid
+graph LR
+
+输入变量_1 --输入--> 表达式
+输入变量_2 --输入--> 表达式
+
+表达式 --返回--> 输出变量
+
+style 表达式 stroke:#333,stroke-width:3px;
+```
+
+而在**设计态**中,表达式是一种 `ASTNode`,建模中我们只需关注:
+
+- 表达式**使用了哪些变量声明** ?
+- 表达式的**返回类型**是怎么推导的 ?
+
+```mermaid
+graph LR
+
+变量声明_1 --输入--> 设计态中的表达式
+变量声明_2 --输入--> 设计态中的表达式
+设计态中的表达式 --推导--> 返回类型
+
+style 设计态中的表达式 stroke:#333,stroke-width:3px;
+
+```
+
+
+:::info{title="举例:设计态中表达式的推导"}
+
+假设我们有一个用 JavaScript 代码描述的表达式 `ref_var + 1`
+
+表达式**使用了哪些变量声明** ?
+- `ref_var` 标识符对应的变量声明
+
+表达式的**返回类型**是怎么推导的 ?
+- `ref_var` 的类型为 `IntegerType`,则 `ref_var + 1` 的返回类型为 `IntegerType`
+- `ref_var` 的类型为 `NumberType`,则 `ref_var + 1` 的返回类型为 `NumberType`
+- `ref_var` 的类型为 `StringType`,则 `ref_var + 1` 的返回类型为 `StringType`
+
+:::
+
+:::info{title="举例:变量引擎如何实现类型推导 + 联动"}
+
+<div style={{  }}>
+  <div style={{ width: 500 }}>
+     <img loading="lazy" src="/variable/variable-batch-auto-infer.gif" alt="类型自动推导" style={{ width: "100%"}} />
+  </div>
+
+  <div style={{ minWidth: 500 }}>
+
+
+图中展示了一个常见的例子:批处理节点引用前序节点的输出变量,对其进行遍历处理,得到一个 item 变量。其中 item 的变量类型会随着前序节点输出变量的类型而自动变化。
+
+这个例子的 ASTNodeJSON 可表示为:
+
+```tsx
+ASTFactory.createVariableDeclaration({
+  key: 'item',
+  initializer: ASTFactory.createEnumerateExpression({
+    enumerateFor: ASTFactory.createKeyPathExpression({
+      keyPath: ['start_0', 'arr']
+    })
+  })
+})
+```
+
+变量的推导链路如下:
+
+```mermaid
+graph LR
+
+  Array_String["Array&lt;String&gt;"]
+  Ref_Var["类型为 \n Array&lt;String&gt; \n 的变量"]
+
+  VariableDeclaration --初始值--> EnumerateExpression
+  KeyPathExpression --引用--> Ref_Var
+  KeyPathExpression --返回类型--> Array_String
+  EnumerateExpression --遍历--> KeyPathExpression
+  EnumerateExpression --返回类型--> String
+  VariableDeclaration -.推导的类型.-> String
+  Array_String -.遍历提取子类型.-> String
+  Ref_Var -.类型.-> Array_String
+
+```
+  </div>
+</div>
+
+
+
+
+:::
+
+
+### 作用域链
+
+作用域链(Scope Chain)定义了**一个作用域可以引用哪些作用域的变量**。它是一个抽象类,具体的业务场景可以实现自定义的作用域链。
+
+变量引擎内置了**自由布局作用域链**和**固定布局作用域链**两种作用域链实现。
+
+
+#### 依赖作用域
+
+`依赖作用域` = 作用域可以访问哪些作用域的输出变量
+
+可以通过 `scope.depScopes` 访问作用域的`依赖作用域`。
+
+
+#### 覆盖作用域
+
+`覆盖作用域` = 作用域的输出变量可以被哪些作用域访问
+
+可以通过 `scope.coverScopes` 访问作用域的`覆盖作用域`。
+
+
+## 画布中的变量
+
+FlowGram 在画布中定义了以下几种特殊的作用域:
+
+### 节点作用域
+
+又称`节点公开作用域`,作用域可以访问**上游节点**的`节点作用域`的变量,同时其输出变量声明也可以被**下游节点**的`节点作用域`访问。
+
+`节点作用域` 可以通过 `node.scope` 来设置和获取,它的作用域链关系如下图所示:
+
+```mermaid
+graph BT
+
+  subgraph 当前节点
+    子节点_1.scope
+    子节点_2.scope
+    当前节点.scope
+  end
+
+  子节点_1.scope -.依赖.-> 上游节点.scope
+  子节点_2.scope -.依赖.-> 上游节点.scope
+  当前节点.scope -.依赖.-> 上游节点.scope
+  下游节点.scope -.依赖.-> 上游节点.scope
+  下游节点.scope -.依赖.-> 当前节点.scope
+
+  下游节点.scope -.-|"<font color=red>❌ 不可访问</font>"| 子节点_1.scope
+  下游节点.scope -.-|"<font color=red>❌ 不可访问</font>"| 子节点_2.scope
+
+  linkStyle 5 stroke:red,stroke-width:2px
+  linkStyle 6 stroke:red,stroke-width:2px
+
+
+  style 当前节点.scope fill:#f9f,stroke:#333,stroke-width:3px
+```
+
+:::warning
+
+在默认的作用域逻辑中,子节点的 `节点作用域` 输出变量不可被**父节点的下游节点** 访问。
+
+:::
+
+
+### 节点私有作用域
+
+`节点私有作用域`的输出变量只能在**当前节点**的`节点作用域`及其**子节点**的`节点作用域`中访问。类似编程语言中`私有变量`的概念。
+
+`节点私有作用域` 可以通过 `node.privateScope` 来设置和获取,它的作用域链关系如下图所示:
+
+```mermaid
+graph BT
+  subgraph 当前节点
+    子节点_1.scope -.依赖.-> 当前节点.privateScope
+    当前节点.scope -.依赖.-> 当前节点.privateScope
+    子节点_2.scope -.依赖.-> 当前节点.privateScope
+  end
+
+  当前节点 -.都依赖.-> 上游节点.scope
+  下游节点.scope -.依赖.-> 当前节点.scope
+  下游节点.scope -.依赖.-> 上游节点.scope
+
+
+  style 当前节点.privateScope fill:#f9f,stroke:#333,stroke-width:3px
+  style 当前节点.scope stroke:#333,stroke-width:3px
+```
+
+
+### 全局作用域
+
+`全局作用域`的变量能被**所有节点作用域和节点私有作用域**访问,但是不能访问其他作用域的变量。
+
+全局作用域的设置方式详见[输出全局变量](./variable-output#输出全局变量),他的作用域链关系如下图所示:
+
+```mermaid
+graph RL
+
+ subgraph 当前节点
+    子节点_1.scope
+    子节点_2.scope
+    当前节点.scope
+    当前节点.privateScope
+  end
+
+  当前节点.scope -.依赖.-> 全局作用域
+  上游节点.scope -.依赖.-> 全局作用域
+  下游节点.scope -.依赖.-> 全局作用域
+  当前节点.privateScope -.依赖.-> 全局作用域
+  子节点_1.scope -.依赖.-> 全局作用域
+  子节点_2.scope -.依赖.-> 全局作用域
+
+  style 当前节点.scope stroke:#333,stroke-width:3px
+  style 全局作用域 fill:#f9f,stroke:#333,stroke-width:3px
+
+```
+
+
+
+## 整体架构
+
+![架构图](/variable/concept/arch-zh.png)
+
+变量引擎设计上遵循 DIP(依赖反转)原则,按照 代码稳定性、抽象层次 以及和 业务的远近 分为三层:
+
+### 变量抽象层
+
+变量架构中抽象层次最高,代码也最为稳定的部分。抽象层对 `ASTNode`、`Scope`、`ScopeChain` 等核心概念进行了抽象类定义。
+
+### 变量实现层
+
+变量架构中变动较大,不同业务之间可能存在调整的部分。引擎内置了一批较为稳定的 `ASTNode` 节点和 `ScopeChain` 的实现。当用户存在复杂的变量需求时,可以通过依赖注入注册新的 `ASTNode` 或者重载已有 `ASTNode` 节点实现定制化。
+
+### 变量物料层
+
+基于外观模式(Facade)的思路提高变量易用性,将复杂的变量逻辑封装为简单开箱即用的变量物料。
+
+- 变量物料的使用详见:[物料](/materials/introduction)
+

+ 16 - 0
apps/docs/src/zh/guide/variable/core-api.mdx

@@ -0,0 +1,16 @@
+---
+description: 介绍变量引擎的底层 API 的设计与使用
+---
+
+# 底层 API (WIP)
+
+## Scope
+
+
+
+
+## ASTNode
+
+
+
+

+ 207 - 0
apps/docs/src/zh/guide/variable/core-ast.mdx

@@ -0,0 +1,207 @@
+---
+description: 介绍变量引擎的内置 AST 节点
+---
+
+# 内置 AST(WIP)
+
+## 声明
+
+### BaseVariableField
+
+`BaseVariableField` 是所有**变量声明**和**属性声明**的 AST 节点的**抽象基类**,它定义了变量字段通用的 `key`、`type`、`initializer` 和 `meta` 属性。
+
+`BaseVariableField` 使用示例如下:
+
+```tsx
+// 通过 getByKeyPath 获取到变量字段
+const varField = scope.available.getByKeyPath(["custom_node_0", "query"])
+
+// 获取变量字段上的信息
+console.log(varField.key); // query
+console.log(varField.keyPath); // ["custom_node_0", "query"]
+console.log(varField.meta); // { title: 'Query }
+console.log(varField.type.kind); // String
+console.log(varField.initializer.kind); // KeyPathExpression
+
+// 获取 ASTNodeJSON
+console.log(varField.toJSON()); // 输出变量声明的 JSON 结构
+
+// 获取下钻字段
+console.log(varField.getByKeyPath(['a', 'b', 'c']));
+
+// 更新类型
+varField.updateType(ASTFactory.createNumber());
+
+// 更新 meta 元信息
+varField.updateMeta({ title: 'Query 2' });
+
+// 更新 initializer
+varField.updateInitializer(ASTFactory.createKeyPathExpression({
+  keyPath: ['start_0', 'query'],
+}));
+
+// 监听变量类型变化
+const disposable = varField.onTypeChange((typeAST) => {
+  console.log(typeAST.kind);
+})
+```
+
+`VariableDeclaration` 和 `Property` 都继承自 `BaseVariableField`,两者的区别为:
+
+- `VariableDeclaration` 用于表示变量的声明和初始化,没有父 Field
+- `Property` 用于表示 Object 对象上的一个属性声明,有父 Field,父 Field 可以是其他 `Property` 或者 `VariableDeclaration`
+
+
+### VariableDeclaration
+
+`VariableDeclaration` 继承自 `BaseVariableField`,是作用域进行变量声明语句的 AST 节点,用于表示变量的声明和初始化。
+
+通过内置的 `ASTFactory` 快速创建 `VariableDeclaration`:
+
+```tsx
+import { ASTFactory, ASTKind } from '@flowgram/editor';
+
+/**
+ * 通过 ASTFactory 创建一个变量声明,类型为 String 类型
+ *
+ * Equals To Plain JSON:
+ * ```json
+ * {
+ *   "kind": "VariableDeclaration",
+ *   "key": "my_unique_variable",
+ *   "meta": { "title": "My Variable" },
+ *   "order": 0,
+ *   "type": { "kind": "String" }
+ * }
+ * ```
+ *
+ * Similar to js code:
+ * ```js
+ * const my_unique_variable: string;
+ * ```
+ */
+scope.setVar(
+  ASTFactory.createVariableDeclaration({
+    key: 'my_unique_variable',
+    meta: { title: 'My Variable' },
+    type: ASTFactory.createString(),
+    order: 0,
+  })
+)
+
+/**
+ * 通过 ASTFactory 创建一个变量声明,声明的右值为指向 start_0.query 的 keyPath 表达式
+ *
+ * Equals To Plain JSON:
+ * ```json
+ * {
+ *   "kind": "VariableDeclaration",
+ *   "key": "my_unique_variable_2",
+ *   "meta": { "title": "My Variable 2" },
+ *   "order": 1,
+ *   "initializer": { "kind": "KeyPathExpression", "keyPath": ["start_0", "query"] }
+ * }
+ * ```
+ *
+ * Similar to js code:
+ * ```js
+ * const my_unique_variable_2 = start_0.query;
+ * ```
+ */
+scope_2.setVar(
+  ASTFactory.createVariableDeclaration({
+    key: 'my_unique_variable_2',
+    meta: { title: 'My Variable 2' },
+    initializer: ASTFactory.createKeyPathExpression({
+      keyPath: ['start_0', 'query'],
+    }),
+    order: 1,
+  })
+)
+```
+
+
+变量引擎将 `VariableDeclaration` 实例化后,可以获取变量声明上的信息,并进行更新、监听等操作:
+
+```tsx
+import { ASTMatch } from "@flowgram/editor";
+
+const varDecl = scope.getVar<VariableDeclaration>();
+
+// 判断 AST 节点是否为 VariableDeclaration
+if(!ASTMatch.isVariableDeclaration(varDecl)) {
+  throw new Error('AST 节点不是 VariableDeclaration 类型');
+}
+
+// 获取变量声明上的信息
+console.log(varDecl.key); // my_unique_variable_2
+console.log(varDecl.meta); // { title: 'My Variable 2' }
+console.log(varDecl.type.kind); // String
+console.log(varDecl.initializer); // KeyPathExpression
+
+// 获取 ASTNodeJSON
+console.log(varDecl.toJSON()); // 输出变量声明的 JSON 结构
+
+// 获取下钻字段
+console.log(varDecl.getByKeyPath(['a', 'b', 'c']));
+
+// 更新类型
+varDecl.updateType(ASTFactory.createNumber());
+
+// 更新 initializer
+varDecl.updateInitializer(ASTFactory.createKeyPathExpression({
+  keyPath: ['start_0', 'query'],
+}));
+
+// 监听变量类型变化
+const disposable = varDecl.onTypeChange((typeAST) => {
+  console.log(typeAST.kind);
+})
+```
+
+
+### Property
+
+
+
+
+### VariableDeclarationList
+
+
+
+## 类型
+
+### BaseType
+
+
+### StringType
+
+
+### NumberType
+
+
+### IntegerType
+
+
+### ObjectType
+
+
+### ArrayType
+
+
+### MapType
+
+
+
+## 表达式
+
+### BaseExpression
+
+
+### EnumerateExpression
+
+
+### WrapArrayExpression
+
+
+

+ 16 - 0
apps/docs/src/zh/guide/variable/custom-scope-chain.mdx

@@ -0,0 +1,16 @@
+---
+description: 介绍如何定制作用域链
+---
+
+# 定制作用域链(WIP)
+
+## 常见场景
+
+
+## 配置 variableEngine.chainConfig
+
+## 通过 ScopeChainTransformService
+
+
+
+## 通过插件定制作用域链

+ 14 - 9
apps/docs/src/zh/guide/variable/variable-consume.mdx

@@ -1,10 +1,16 @@
+---
+description: 介绍如何在 FlowGram 中消费变量引擎输出的变量
+---
+
 # 消费变量
 # 消费变量
 
 
-在 Flowgram 中,变量的管理和消费是构建动态、可交互应用的核心。
+在 FlowGram 中,当一个节点想要使用到前序节点的变量,就需要消费变量
 
 
 ## 官方物料:`VariableSelector`
 ## 官方物料:`VariableSelector`
 
 
-为了让你能更轻松地在应用中集成变量选择的功能,官方物料提供了 `VariableSelector` 组件。详情可以阅读文档: [VariableSelector](/materials/components/variable-selector)
+为了让你能更轻松地在应用中集成变量选择的功能,官方物料提供了 `VariableSelector` 组件。
+
+详见文档: [VariableSelector](/materials/components/variable-selector)
 
 
 
 
 ## 在节点内获取变量树
 ## 在节点内获取变量树
@@ -15,14 +21,13 @@
 
 
 `useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。
 `useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {7}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   useAvailableVariables,
   useAvailableVariables,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 // .... 在 React 组件或 Hook 中
 // .... 在 React 组件或 Hook 中
-
 const availableVariables = useAvailableVariables();
 const availableVariables = useAvailableVariables();
 
 
 const renderVariable = (variable: BaseVariableField) => {
 const renderVariable = (variable: BaseVariableField) => {
@@ -39,7 +44,7 @@ return availableVariables.map(renderVariable);
 
 
 当变量的类型是 `Object` 时,我们往往需要能够“下钻”到它的内部,获取其属性。`ASTMatch.isObject` 方法可以帮助我们判断一个变量类型是否为对象。如果是,我们就可以递归地渲染它的 `properties`。
 当变量的类型是 `Object` 时,我们往往需要能够“下钻”到它的内部,获取其属性。`ASTMatch.isObject` 方法可以帮助我们判断一个变量类型是否为对象。如果是,我们就可以递归地渲染它的 `properties`。
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {12}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   ASTMatch,
   ASTMatch,
@@ -62,7 +67,7 @@ const renderVariable = (variable: BaseVariableField) => ({
 
 
 与 `Object` 类型类似,当遇到 `Array` 类型的变量时,我们也希望能展示它的内部结构。对于数组,我们通常关心的是其元素的类型。`ASTMatch.isArray` 可以判断变量类型是否为数组。值得注意的是,数组的元素类型可能是任意的,甚至可能是另一个数组。因此,我们需要一个递归的辅助函数 `getTypeChildren` 来处理这种情况。
 与 `Object` 类型类似,当遇到 `Array` 类型的变量时,我们也希望能展示它的内部结构。对于数组,我们通常关心的是其元素的类型。`ASTMatch.isArray` 可以判断变量类型是否为数组。值得注意的是,数组的元素类型可能是任意的,甚至可能是另一个数组。因此,我们需要一个递归的辅助函数 `getTypeChildren` 来处理这种情况。
 
 
-```tsx pure title="use-variable-tree.tsx"
+```tsx pure title="use-variable-tree.tsx" {13,16}
 import {
 import {
   type BaseVariableField,
   type BaseVariableField,
   type BaseType,
   type BaseType,
@@ -120,7 +125,7 @@ console.log(available.variables);
 
 
 最基础的用法就是获取当前作用域下所有可用的变量。
 最基础的用法就是获取当前作用域下所有可用的变量。
 
 
-```tsx
+```tsx {4,7}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 
 
 function MyComponent() {
 function MyComponent() {
@@ -145,7 +150,7 @@ function MyComponent() {
 
 
 假设我们有一个名为 `user` 的 Object 类型变量,它有一个 `name` 属性。我们希望在 `user.name` 变化时更新组件。
 假设我们有一个名为 `user` 的 Object 类型变量,它有一个 `name` 属性。我们希望在 `user.name` 变化时更新组件。
 
 
-```tsx
+```tsx {13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 
 
@@ -190,7 +195,7 @@ function UserNameDisplay() {
 
 
 让我们通过一个具体的例子来看看如何在组件中使用这些 API。
 让我们通过一个具体的例子来看看如何在组件中使用这些 API。
 
 
-```tsx
+```tsx {9-11,14-16,19-22}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
 
 

+ 35 - 56
apps/docs/src/zh/guide/variable/variable-output.mdx

@@ -1,3 +1,7 @@
+---
+description: 介绍如何在 FlowGram 中使用变量引擎输出变量
+---
+
 # 输出变量
 # 输出变量
 
 
 我们主要将输出变量分为三类:
 我们主要将输出变量分为三类:
@@ -22,7 +26,7 @@
 
 
 只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
 只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {8-9}
 import {
 import {
   syncVariableTitle,
   syncVariableTitle,
   provideJsonSchemaOutputs,
   provideJsonSchemaOutputs,
@@ -43,7 +47,7 @@ export const formMeta = {
 
 
 :::note
 :::note
 
 
-Flowgram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `parse`函数,就可以自定义自己的变量同步副作用:
+FlowGram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `parse`函数,就可以自定义自己的变量同步副作用:
 - `parse` 会在表单值初始化和更新时被调用
 - `parse` 会在表单值初始化和更新时被调用
 - `parse` 的输入为当前字段的表单的值
 - `parse` 的输入为当前字段的表单的值
 - `parse` 的输出为变量 AST 信息
 - `parse` 的输出为变量 AST 信息
@@ -52,7 +56,7 @@ Flowgram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `p
 
 
 下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量:
 下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量:
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {27-38,41-52}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -82,13 +86,13 @@ export const formMeta =  {
     'path.to.value': createEffectFromVariableProvider({
     'path.to.value': createEffectFromVariableProvider({
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
             title: `Your Output Variable Title`,
             title: `Your Output Variable Title`,
           },
           },
           key: `uid_${node.id}`,
           key: `uid_${node.id}`,
           type: createTypeFromValue(v)
           type: createTypeFromValue(v)
-        }
+        }]
       }
       }
     }),
     }),
     // Create second variable
     // Create second variable
@@ -96,13 +100,13 @@ export const formMeta =  {
     'path.to.value2': createEffectFromVariableProvider({
     'path.to.value2': createEffectFromVariableProvider({
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
             title: `Your Output Variable Title 2`,
             title: `Your Output Variable Title 2`,
           },
           },
           key: `uid_${node.id}_2`,
           key: `uid_${node.id}_2`,
           type: createTypeFromValue(v)
           type: createTypeFromValue(v)
-        }
+        }]
       }
       }
     }),
     }),
   },
   },
@@ -117,7 +121,7 @@ export const formMeta =  {
 如果多个字段同步到一个变量,就需要用到 `createEffectFromVariableProvider` 的 `namespace` 字段,将多个字段的变量数据同步到同一个命名空间上。
 如果多个字段同步到一个变量,就需要用到 `createEffectFromVariableProvider` 的 `namespace` 字段,将多个字段的变量数据同步到同一个命名空间上。
 
 
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {11}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -132,16 +136,13 @@ const variableSyncEffect = createEffectFromVariableProvider({
 
 
   // 将表单值解析为变量
   // 将表单值解析为变量
   parse(_, { form, node }) {
   parse(_, { form, node }) {
-    return {
+    return [{
       meta: {
       meta: {
-        title: `Your Output Variable Title`,
+        title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
       },
       },
       key: `uid_${node.id}`,
       key: `uid_${node.id}`,
-      type: createTypeFromValue({
-        value1: form.getValueIn('path.to.value'),
-        value2: form.getValueIn('path.to.value2'),
-      })
-    }
+      type: ASTFactory.createCustomType({ typeName: "CustomVariableType" })
+    }]
   }
   }
 })
 })
 
 
@@ -174,7 +175,7 @@ export const formMeta = {
 :::
 :::
 
 
 
 
-```tsx pure title="form-meta.tsx"
+```tsx pure title="form-meta.tsx" {10-18,29-38}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
 
 
 export const formMeta = {
 export const formMeta = {
@@ -187,10 +188,10 @@ export const formMeta = {
         context.node.scope.setVar(
         context.node.scope.setVar(
           ASTFactory.createVariableDeclaration({
           ASTFactory.createVariableDeclaration({
             meta: {
             meta: {
-              title: `Your Output Variable Title`,
+              title: `Title_${value}`,
             },
             },
             key: `uid_${node.id}`,
             key: `uid_${node.id}`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createString(),
           })
           })
         )
         )
 
 
@@ -207,10 +208,10 @@ export const formMeta = {
           'namespace_2',
           'namespace_2',
           ASTFactory.createVariableDeclaration({
           ASTFactory.createVariableDeclaration({
             meta: {
             meta: {
-              title: `Your Output Variable Title 2`,
+              title: `Title_${value}`,
             },
             },
             key: `uid_${node.id}_2`,
             key: `uid_${node.id}_2`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createNumber(),
           })
           })
         )
         )
 
 
@@ -235,7 +236,7 @@ export const formMeta = {
 
 
 下面的例子演示了如何在插件的 `onInit`生命周期中,获取开始节点的 `Scope`,并对它的变量进行一系列操作。
 下面的例子演示了如何在插件的 `onInit`生命周期中,获取开始节点的 `Scope`,并对它的变量进行一系列操作。
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {10-22}
 import {
 import {
   FlowDocument,
   FlowDocument,
   definePluginCreator,
   definePluginCreator,
@@ -266,7 +267,7 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
 
 
 下面的例子演示了如何通过 `onNodeCreate` 获取到新创建节点的 Scope,并通过监听 `node.form.onFormValuesChange` 实现变量的同步操作。
 下面的例子演示了如何通过 `onNodeCreate` 获取到新创建节点的 Scope,并通过监听 `node.form.onFormValuesChange` 实现变量的同步操作。
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {10,29}
 import {
 import {
   FlowDocument,
   FlowDocument,
   definePluginCreator,
   definePluginCreator,
@@ -322,7 +323,7 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
 
 
 下面的例子演示了如何在 `formMeta.render` 中,通过 `useCurrentScope` 事件,同步更新变量。
 下面的例子演示了如何在 `formMeta.render` 中,通过 `useCurrentScope` 事件,同步更新变量。
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {13}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -366,26 +367,7 @@ export const formMeta = {
 
 
 ## 输出节点私有变量
 ## 输出节点私有变量
 
 
-私有变量是指只能在当前节点及其子节点中访问的变量。
-
-私有变量可以通过私有作用域 `node.privateScope` 来设置和获取,它的作用域链关系如下图所示:
-
-```mermaid
-graph BT
-  subgraph 当前节点
-    子节点_1.scope -.依赖变量.-> 当前节点.privateScope
-    当前节点.scope -.依赖变量.-> 当前节点.privateScope
-    子节点_2.scope -.依赖变量.-> 当前节点.privateScope
-  end
-
-  当前节点 -.都依赖变量.-> 上游节点.scope
-  下游节点.scope -.依赖变量.-> 当前节点.scope
-  下游节点.scope -.依赖变量.-> 上游节点.scope
-
-
-  style 当前节点.privateScope fill:#f9f,stroke:#333,stroke-width:3px
-  style 当前节点.scope stroke:#333,stroke-width:3px
-```
+私有变量是指只能在当前节点及其子节点中访问的变量。(详见:[节点私有作用域](./concept#节点私有作用域))
 
 
 下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
 下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
 
 
@@ -395,7 +377,7 @@ graph BT
 - `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
 - `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
 - `scope` 设置为 `public` 时,变量的作用域为当前节点的作用域 `node.scope`
 - `scope` 设置为 `public` 时,变量的作用域为当前节点的作用域 `node.scope`
 
 
-```tsx pure title="form-meta.ts"
+```tsx pure title="form-meta.ts" {11}
 import {
 import {
   createEffectFromVariableProvider,
   createEffectFromVariableProvider,
   ASTFactory,
   ASTFactory,
@@ -409,13 +391,13 @@ export const formMeta =  {
       scope: 'private',
       scope: 'private',
       // parse form value to variable
       // parse form value to variable
       parse(v: string) {
       parse(v: string) {
-        return {
+        return [{
           meta: {
           meta: {
-            title: `Your Private Variable Title`,
+            title: `Private_${v}`,
           },
           },
           key: `uid_${node.id}_locals`,
           key: `uid_${node.id}_locals`,
-          type: createTypeFromValue(v)
-        }
+          type: ASTFactory.createBoolean(),
+        }]
       }
       }
     }),
     }),
   },
   },
@@ -432,7 +414,7 @@ export const formMeta =  {
 `node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
 `node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
 
 
 
 
-```tsx pure title="form-meta.tsx"
+```tsx pure title="form-meta.tsx" {10-18}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
 
 
 export const formMeta = {
 export const formMeta = {
@@ -448,7 +430,7 @@ export const formMeta = {
               title: `Your Private Variable Title`,
               title: `Your Private Variable Title`,
             },
             },
             key: `uid_${node.id}`,
             key: `uid_${node.id}`,
-            type: createTypeFromValue(value),
+            type: ASTFactory.createInteger(),
           })
           })
         )
         )
 
 
@@ -477,14 +459,13 @@ export const formMeta = {
 在插件的上下文中(`ctx`),我们可以直接“注入”`GlobalScope` 的实例:
 在插件的上下文中(`ctx`),我们可以直接“注入”`GlobalScope` 的实例:
 
 
 
 
-```tsx pure title="global-variable-plugin.tsx"
+```tsx pure title="global-variable-plugin.tsx" {10-20}
 import {
 import {
   GlobalScope,
   GlobalScope,
   definePluginCreator,
   definePluginCreator,
   PluginCreator
   PluginCreator
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
-
 export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
 export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
     onInit(ctx, options) {
     onInit(ctx, options) {
@@ -509,14 +490,13 @@ export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions
 
 
 如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例:
 如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例:
 
 
-```tsx pure title="global-variable-component.tsx"
+```tsx pure title="global-variable-component.tsx" {7}
 import {
 import {
   GlobalScope,
   GlobalScope,
   useService,
   useService,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 function GlobalVariableComponent() {
 function GlobalVariableComponent() {
-
   const globalScope = useService(GlobalScope)
   const globalScope = useService(GlobalScope)
 
 
   // ...
   // ...
@@ -546,7 +526,7 @@ function GlobalVariableComponent() {
 
 
 下面是一个在插件中操作全局变量的综合示例:
 下面是一个在插件中操作全局变量的综合示例:
 
 
-```tsx pure title="sync-variable-plugin.tsx"
+```tsx pure title="sync-variable-plugin.tsx" {11-39}
 import {
 import {
   GlobalScope,
   GlobalScope,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
@@ -571,7 +551,6 @@ onInit(ctx, options) {
 
 
   globalScope.clearVar()
   globalScope.clearVar()
 
 
-
   // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
   // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
     globalScope.setVar(
     globalScope.setVar(
       'namespace_1',
       'namespace_1',

+ 11 - 8
packages/plugins/node-variable-plugin/src/form-v2/create-provider-effect.ts

@@ -33,16 +33,19 @@ export function createEffectFromVariableProvider(
     const { node } = context;
     const { node } = context;
     const scope = getScope(node);
     const scope = getScope(node);
 
 
+    const parsedValue = options.parse(value, {
+      node,
+      scope,
+      options,
+      name,
+      formValues,
+      form,
+    });
+
+    // Fix: When parsedValue is not an array, transform it to array
     scope.ast.set(options.namespace || name || '', {
     scope.ast.set(options.namespace || name || '', {
       kind: ASTKind.VariableDeclarationList,
       kind: ASTKind.VariableDeclarationList,
-      declarations: options.parse(value, {
-        node,
-        scope,
-        options,
-        name,
-        formValues,
-        form,
-      }),
+      declarations: Array.isArray(parsedValue) ? parsedValue : [parsedValue],
     });
     });
   };
   };