Explorar o código

feat(variable): optimize comments and rename KeyPathExpressionV2 to KeyPathExpression (#929)

* feat(variable): optimize scope provider api

* docs(variable): optimize variable-core comments

* chore(variable): rename KeyPathExpressionV2 to KeyPathExpression

* feat: export legacy key path expression

* fix: typescript compat

* fix(variable): React
Yiwei Mao hai 2 meses
pai
achega
1daa5596ab
Modificáronse 67 ficheiros con 1800 adicións e 636 borrados
  1. 14 1
      apps/docs/scripts/auto-generate.ts
  2. 15 17
      apps/docs/src/en/guide/variable/variable-output.mdx
  3. 15 11
      apps/docs/src/zh/guide/variable/variable-output.mdx
  4. 4 1
      packages/plugins/node-variable-plugin/src/components/PrivateScopeProvider.tsx
  5. 4 1
      packages/plugins/node-variable-plugin/src/components/PublicScopeProvider.tsx
  6. 0 24
      packages/variable-engine/variable-core/README.md
  7. 2 2
      packages/variable-engine/variable-core/__tests__/ast/__snapshots__/variable-declaration.test.ts.snap
  8. 13 20
      packages/variable-engine/variable-core/__tests__/ast/key-path-expression-v2.test.ts
  9. 74 50
      packages/variable-engine/variable-core/src/ast/ast-node.ts
  10. 11 14
      packages/variable-engine/variable-core/src/ast/ast-registers.ts
  11. 16 1
      packages/variable-engine/variable-core/src/ast/common/data-node.ts
  12. 24 4
      packages/variable-engine/variable-core/src/ast/common/list-node.ts
  13. 26 8
      packages/variable-engine/variable-core/src/ast/common/map-node.ts
  14. 65 8
      packages/variable-engine/variable-core/src/ast/declaration/base-variable-field.ts
  15. 7 1
      packages/variable-engine/variable-core/src/ast/declaration/property.ts
  16. 28 8
      packages/variable-engine/variable-core/src/ast/declaration/variable-declaration-list.ts
  17. 21 11
      packages/variable-engine/variable-core/src/ast/declaration/variable-declaration.ts
  18. 18 9
      packages/variable-engine/variable-core/src/ast/expression/base-expression.ts
  19. 28 4
      packages/variable-engine/variable-core/src/ast/expression/enumerate-expression.ts
  20. 2 2
      packages/variable-engine/variable-core/src/ast/expression/index.ts
  21. 0 120
      packages/variable-engine/variable-core/src/ast/expression/keypath-expression-v2.ts
  22. 84 19
      packages/variable-engine/variable-core/src/ast/expression/keypath-expression.ts
  23. 119 0
      packages/variable-engine/variable-core/src/ast/expression/legacy-keypath-expression.ts
  24. 30 3
      packages/variable-engine/variable-core/src/ast/expression/wrap-array-expression.ts
  25. 75 5
      packages/variable-engine/variable-core/src/ast/factory.ts
  26. 29 7
      packages/variable-engine/variable-core/src/ast/flags.ts
  27. 74 6
      packages/variable-engine/variable-core/src/ast/match.ts
  28. 38 6
      packages/variable-engine/variable-core/src/ast/type/array.ts
  29. 16 7
      packages/variable-engine/variable-core/src/ast/type/base-type.ts
  30. 7 0
      packages/variable-engine/variable-core/src/ast/type/boolean.ts
  31. 22 1
      packages/variable-engine/variable-core/src/ast/type/custom-type.ts
  32. 7 0
      packages/variable-engine/variable-core/src/ast/type/integer.ts
  33. 36 22
      packages/variable-engine/variable-core/src/ast/type/map.ts
  34. 7 0
      packages/variable-engine/variable-core/src/ast/type/number.ts
  35. 43 13
      packages/variable-engine/variable-core/src/ast/type/object.ts
  36. 10 2
      packages/variable-engine/variable-core/src/ast/type/string.ts
  37. 3 0
      packages/variable-engine/variable-core/src/ast/type/union.ts
  38. 113 28
      packages/variable-engine/variable-core/src/ast/types.ts
  39. 12 8
      packages/variable-engine/variable-core/src/ast/utils/expression.ts
  40. 4 4
      packages/variable-engine/variable-core/src/ast/utils/helpers.ts
  41. 2 2
      packages/variable-engine/variable-core/src/ast/utils/inversify.ts
  42. 1 1
      packages/variable-engine/variable-core/src/ast/utils/variable-field.ts
  43. 8 1
      packages/variable-engine/variable-core/src/providers.ts
  44. 52 4
      packages/variable-engine/variable-core/src/react/context.tsx
  45. 4 1
      packages/variable-engine/variable-core/src/react/hooks/useAvailableVariables.ts
  46. 12 3
      packages/variable-engine/variable-core/src/react/hooks/useScopeAvailable.ts
  47. 1 1
      packages/variable-engine/variable-core/src/react/index.tsx
  48. 42 26
      packages/variable-engine/variable-core/src/scope/datas/scope-available-data.ts
  49. 18 0
      packages/variable-engine/variable-core/src/scope/datas/scope-event-data.ts
  50. 25 11
      packages/variable-engine/variable-core/src/scope/datas/scope-output-data.ts
  51. 18 7
      packages/variable-engine/variable-core/src/scope/scope-chain.ts
  52. 46 23
      packages/variable-engine/variable-core/src/scope/scope.ts
  53. 72 6
      packages/variable-engine/variable-core/src/scope/types.ts
  54. 55 23
      packages/variable-engine/variable-core/src/scope/variable-table.ts
  55. 29 4
      packages/variable-engine/variable-core/src/services/variable-field-key-rename-service.ts
  56. 1 1
      packages/variable-engine/variable-core/src/utils/memo.ts
  57. 5 0
      packages/variable-engine/variable-core/src/utils/toDisposable.tsx
  58. 9 5
      packages/variable-engine/variable-core/src/variable-container-module.ts
  59. 62 16
      packages/variable-engine/variable-core/src/variable-engine.ts
  60. 62 30
      packages/variable-engine/variable-layout/src/chains/fixed-layout-scope-chain.ts
  61. 54 15
      packages/variable-engine/variable-layout/src/chains/free-layout-scope-chain.ts
  62. 17 0
      packages/variable-engine/variable-layout/src/flow-node-variable-data.ts
  63. 11 0
      packages/variable-engine/variable-layout/src/scopes/global-scope.ts
  64. 34 1
      packages/variable-engine/variable-layout/src/services/scope-chain-transform-service.ts
  65. 33 1
      packages/variable-engine/variable-layout/src/types.ts
  66. 6 4
      packages/variable-engine/variable-layout/src/utils.ts
  67. 5 2
      packages/variable-engine/variable-layout/src/variable-chain-config.ts

+ 14 - 1
apps/docs/scripts/auto-generate.ts

@@ -14,6 +14,9 @@ import { Application, TSConfigReader, ProjectReflection } from 'typedoc';
 import { patchGeneratedApiDocs } from './patch';
 import { docLabelMap, overviewMetaJson } from './constants';
 
+// 只生成指定的包文档,本地调试用
+const gen_pkgs = process.argv.slice(2) || [];
+
 async function generateDocs() {
   // @ts-ignore
   const projectRoot = path.resolve(__dirname, '../../../'); // Rush 项目根目录
@@ -29,9 +32,19 @@ async function generateDocs() {
 
   for (const firstLevel of firstLevelFiles) {
     const firstLevelPath = path.resolve(packagesDir, firstLevel);
+
+    // check if it is a directory
+    if (!(await fs.stat(firstLevelPath)).isDirectory()) {
+      continue;
+    }
+
     const packageNames = await fs.readdir(firstLevelPath); // 异步读取包目录
 
     for (const packageName of packageNames) {
+      if (gen_pkgs.length > 0 && !gen_pkgs.includes(packageName)) {
+        continue;
+      }
+
       const packagePath = path.join(firstLevelPath, packageName);
       const packageJsonPath = path.join(packagePath, 'package.json');
       const tsconfigPath = path.join(packagePath, 'tsconfig.json');
@@ -58,7 +71,7 @@ async function generateDocs() {
           out: packageOutputDir,
           plugin: ['typedoc-plugin-markdown'], // 使用 Markdown 插件
           theme: 'markdown', // Markdown 模式不依赖 HTML 主题
-          exclude: ['**/__tests__/**', 'vitest.config.ts', 'vitest.setup.ts'],
+          exclude: ['**/__tests__/**', 'vitest.config.ts', 'vitest.setup.ts', '**/.DS_Store'],
           basePath: packagePath,
           excludePrivate: true,
           excludeProtected: true,

+ 15 - 17
apps/docs/src/en/guide/variable/variable-output.mdx

@@ -12,7 +12,7 @@ Outputting a node variable means that this variable is bound to the life cycle o
 
 We usually have three ways to output node variables:
 
-### Method 1: Sync through 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.
 
@@ -20,8 +20,6 @@ We usually have three ways to output node variables:
 
 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 can automatically convert the JSON Schema of the form into the output variable of the node without additional configuration.
-
 It is very simple to use, just add two lines of configuration in the `effect` of `formMeta`:
 
 ```tsx pure title="form-meta.ts"
@@ -38,20 +36,18 @@ export const formMeta = {
 };
 ```
 
-#### Custom Output via `createEffectFromVariableProvider`
-
-Although `provideJsonSchemaOutputs` is convenient, it only adapts to JsonSchema.
+#### Via `createEffectFromVariableProvider`
 
-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.
 
-Flowgram provides a powerful helper function `createEffectFromVariableProvider`
+:::note
 
-You only need to define a `parse` function to customize your own variable synchronization logic:
-- The `parse` function will be called when the form value is initialized and updated
-- The input of the `parse` function is the value of the current field's form
-- The output of the `parse` function is the variable AST information
+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
+- The input of `parse` is the value of the current field's form
+- The output of `parse` is the variable AST information
 
-<br />
+:::
 
 In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`:
 
@@ -162,6 +158,8 @@ export const formMeta = {
 
 If `createEffectFromVariableProvider` does not meet your needs, you can also directly use the `node.scope` API in form side effects for more flexible and variable operations.
 
+:::note
+
 `node.scope` will return a node's variable scope (Scope) object, which has several core methods:
 
 - `setVar(variable)`: Set a variable.
@@ -171,6 +169,8 @@ If `createEffectFromVariableProvider` does not meet your needs, you can also dir
 - `clearVar()`: Clear all variables.
 - `clearVar(namespace)`: Clear the variables under the specified namespace.
 
+:::
+
 
 ```tsx pure title="form-meta.tsx"
 import { Effect } from '@flowgram.ai/editor';
@@ -318,8 +318,6 @@ It breaks the principle of **separation of data and rendering**, leading to tigh
 
 :::
 
-The following example demonstrates how to synchronize and update variables through the `useCurrentScope` event in `formMeta.render`.
-
 ```tsx pure title="form-meta.ts"
 import {
   createEffectFromVariableProvider,
@@ -366,12 +364,12 @@ Private variables can be set and obtained through the private scope `node.privat
 ```mermaid
 graph BT
   subgraph Current_Node
-    Current_Node.scope -.depends on.-> Current_Node.privateScope
     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 -.both depend on.-> Upstream_Node.scope
+  Current_Node -.all depend on.-> Upstream_Node.scope
   Downstream_Node.scope -.depends on.-> Current_Node.scope
   Downstream_Node.scope -.depends on.-> Upstream_Node.scope
 

+ 15 - 11
apps/docs/src/zh/guide/variable/variable-output.mdx

@@ -20,9 +20,7 @@
 
 若节点所需输出变量的结构与 [JSON Schema](https://json-schema.org/) 结构匹配,即可使用 `provideJsonSchemaOutputs` 这一副作用(Effect)物料。
 
-它能够自动将表单的 JSON Schema 转换为节点的输出变量,无需额外配置。
-
-使用起来非常简单,只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
+只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
 
 ```tsx pure title="form-meta.ts"
 import {
@@ -40,16 +38,17 @@ export const formMeta = {
 
 #### 通过 `createEffectFromVariableProvider` 自定义输出
 
-`provideJsonSchemaOutputs` 虽然方便,但它只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
 
-Flowgram 提供了一个强大的辅助函数 `createEffectFromVariableProvider`
+`provideJsonSchemaOutputs` 只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
+
+:::note
 
-只需要定义一个 `parse`函数,就可以自定义自己的变量同步逻辑:
-- `parse`函数会在表单值初始化和更新时被调用
-- `parse`函数的输入为当前字段的表单的值
-- `parse`函数的输出为变量 AST 信息
+Flowgram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `parse`函数,就可以自定义自己的变量同步副作用
+- `parse` 会在表单值初始化和更新时被调用
+- `parse` 的输入为当前字段的表单的值
+- `parse` 的输出为变量 AST 信息
 
-<br />
+:::
 
 下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量:
 
@@ -161,6 +160,8 @@ export const formMeta = {
 
 如果 `createEffectFromVariableProvider` 不能满足你的需求,你也可以直接在表单副作用中使用 `node.scope` API,进行更加灵活多变的变量操作。
 
+:::note
+
 `node.scope` 会返回一个节点的变量作用域(Scope)对象,这个对象上挂载了几个核心方法:
 
 - `setVar(variable)`: 设置一个变量。
@@ -170,6 +171,8 @@ export const formMeta = {
 - `clearVar()`: 清空所有变量。
 - `clearVar(namespace)`: 清空指定命名空间下的变量。
 
+:::
+
 
 ```tsx pure title="form-meta.tsx"
 import { Effect } from '@flowgram.ai/editor';
@@ -314,6 +317,7 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
 - **脱离 UI 无法同步变量**:脱离了 UI 就无法独立更新变量,会导致数据和渲染之间的不一致。
 - **增加代码复杂度**:直接在 UI 中操作变量,会增加 UI 逻辑的复杂性,使代码更难维护。
 - **性能问题**:变量的同步操作可能会触发 UI 组件的不必要的重渲染。
+
 :::
 
 下面的例子演示了如何在 `formMeta.render` 中,通过 `useCurrentScope` 事件,同步更新变量。
@@ -369,8 +373,8 @@ export const formMeta = {
 ```mermaid
 graph BT
   subgraph 当前节点
-    当前节点.scope -.依赖变量.-> 当前节点.privateScope
     子节点_1.scope -.依赖变量.-> 当前节点.privateScope
+    当前节点.scope -.依赖变量.-> 当前节点.privateScope
     子节点_2.scope -.依赖变量.-> 当前节点.privateScope
   end
 

+ 4 - 1
packages/plugins/node-variable-plugin/src/components/PrivateScopeProvider.tsx

@@ -12,6 +12,9 @@ interface VariableProviderProps {
   children: React.ReactElement;
 }
 
+/**
+ * PrivateScopeProvider provides the private scope to its children via context.
+ */
 export const PrivateScopeProvider = ({ children }: VariableProviderProps) => {
   const node = useEntityFromContext();
 
@@ -24,5 +27,5 @@ export const PrivateScopeProvider = ({ children }: VariableProviderProps) => {
     return variableData.private!;
   }, [node]);
 
-  return <ScopeProvider value={{ scope: privateScope }}>{children}</ScopeProvider>;
+  return <ScopeProvider scope={privateScope}>{children}</ScopeProvider>;
 };

+ 4 - 1
packages/plugins/node-variable-plugin/src/components/PublicScopeProvider.tsx

@@ -12,10 +12,13 @@ interface VariableProviderProps {
   children: React.ReactElement;
 }
 
+/**
+ * PublicScopeProvider provides the public scope to its children via context.
+ */
 export const PublicScopeProvider = ({ children }: VariableProviderProps) => {
   const node = useEntityFromContext();
 
   const publicScope: Scope = useMemo(() => node.getData(FlowNodeVariableData).public, [node]);
 
-  return <ScopeProvider value={{ scope: publicScope }}>{children}</ScopeProvider>;
+  return <ScopeProvider scope={publicScope}>{children}</ScopeProvider>;
 };

+ 0 - 24
packages/variable-engine/variable-core/README.md

@@ -1,24 +0,0 @@
-# 变量引擎
-
-## 文件夹结构
-
-```
-- ast 可响应AST树实现
-  - common 通用AST节点
-  - declaration 声明AST节点
-  - expression 表达式AST节点
-  - type 类型AST节点
-  - utils 工具函数
-  - ast-node.ts 可响应式AST基类实现
-  - ast-registers.ts AST节点注册器
-- scope 作用域实现
-  - datas 作用域内部的Data
-    - scope-available-data.ts 作用域可用变量
-    - scope-output-data.ts 作用域输出变量
-  - scope-chain.ts 作用域链抽象实现
-  - scope.ts 作用域实体
-  - variable-table.ts 变量快速访问表
-```
-
-## Case Run Down
-

+ 2 - 2
packages/variable-engine/variable-core/__tests__/ast/__snapshots__/variable-declaration.test.ts.snap

@@ -22,8 +22,7 @@ exports[`test Basic Variable Declaration > test remove variable, update variable
 
 exports[`test Basic Variable Declaration > test simple variable declarations 1`] = `
 {
-  "kind": "VariableDeclarationList",
-  "properties": [
+  "declarations": [
     {
       "initializer": undefined,
       "key": "string",
@@ -149,6 +148,7 @@ exports[`test Basic Variable Declaration > test simple variable declarations 1`]
       },
     },
   ],
+  "kind": "VariableDeclarationList",
 }
 `;
 

+ 13 - 20
packages/variable-engine/variable-core/__tests__/ast/key-path-expression-v2.test.ts

@@ -6,13 +6,7 @@
 import { vi, describe, test, expect } from 'vitest';
 
 import { getParentFields } from '../../src/ast/utils/variable-field';
-import {
-  ASTKind,
-  VariableEngine,
-  VariableDeclaration,
-  ASTFactory,
-  KeyPathExpressionV2,
-} from '../../src';
+import { ASTKind, VariableEngine, VariableDeclaration, ASTFactory } from '../../src';
 import { getContainer } from '../../__mocks__/container';
 
 const {
@@ -39,7 +33,6 @@ vi.mock('nanoid', () => {
 describe('test Key Path Expression V2', () => {
   const container = getContainer();
   const variableEngine = container.get(VariableEngine);
-  variableEngine.astRegisters.registerAST(KeyPathExpressionV2);
   const globalScope = variableEngine.createScope('global');
   const testScope = variableEngine.createScope('test');
 
@@ -59,7 +52,7 @@ describe('test Key Path Expression V2', () => {
           }),
         ],
       }),
-    }),
+    })
   );
 
   test('init const test_key_path = source.a', () => {
@@ -70,13 +63,13 @@ describe('test Key Path Expression V2', () => {
         initializer: createKeyPathExpression({
           keyPath: ['source', 'a'],
         }),
-      }),
+      })
     )!;
 
     expect(variableByKeyPath.initializer?.kind).toEqual(ASTKind.KeyPathExpression);
-    expect(variableByKeyPath.initializer?.refs.map(_ref => _ref?.key)).toEqual(['a']);
+    expect(variableByKeyPath.initializer?.refs.map((_ref) => _ref?.key)).toEqual(['a']);
     // variableByKeyPath 的类型重新建立了实例,其父节点为当前节点
-    expect(getParentFields(variableByKeyPath.type).map(_field => _field?.key)).toEqual([
+    expect(getParentFields(variableByKeyPath.type).map((_field) => _field?.key)).toEqual([
       'test_key_path',
     ]);
     expect(variableByKeyPath.type.toJSON()).toEqual({ kind: ASTKind.String });
@@ -88,7 +81,7 @@ describe('test Key Path Expression V2', () => {
     const variableByKeyPath = testScope.output.getVariableByKey('test_key_path')!;
 
     let typeChangeTimes = 0;
-    variableByKeyPath.onTypeChange(type => {
+    variableByKeyPath.onTypeChange((type) => {
       typeChangeTimes++;
       expect([typeChangeTimes, type?.toJSON()]).toMatchSnapshot();
     });
@@ -128,7 +121,7 @@ describe('test Key Path Expression V2', () => {
           createProperty({ key: 'a', type: createString() }),
           createProperty({ key: 'b', type: createNumber() }),
         ],
-      }),
+      })
     );
     expect(variableByKeyPath.type.toJSON()).toEqual({ kind: ASTKind.Number });
     expect(typeChangeTimes).toBe(4);
@@ -139,7 +132,7 @@ describe('test Key Path Expression V2', () => {
         enumerateFor: createKeyPathExpression({
           keyPath: ['source', 'b'],
         }),
-      }),
+      })
     );
     expect(variableByKeyPath.type).toBeUndefined();
     expect(variableByKeyPath.initializer?.refs).toEqual([]);
@@ -158,7 +151,7 @@ describe('test Key Path Expression V2', () => {
             }),
           }),
         ],
-      }),
+      })
     );
     expect(variableByKeyPath.type.toJSON()).toEqual({ kind: ASTKind.Number });
     expect(typeChangeTimes).toBe(6);
@@ -166,7 +159,7 @@ describe('test Key Path Expression V2', () => {
 
     // 原作用域删除,显示为空类型
     globalScope.dispose();
-    expect(variableByKeyPath.scope.depScopes.map(_scope => _scope.id)).toEqual([]);
+    expect(variableByKeyPath.scope.depScopes.map((_scope) => _scope.id)).toEqual([]);
     expect(variableByKeyPath.type).toBeUndefined();
     expect(typeChangeTimes).toBe(7);
   });
@@ -190,7 +183,7 @@ describe('test Key Path Expression V2', () => {
             }),
           ],
         }),
-      }),
+      })
     );
 
     const cycle2Var = cycle2.ast.set<VariableDeclaration>(
@@ -202,7 +195,7 @@ describe('test Key Path Expression V2', () => {
             keyPath: ['cycle1_var', 'a'],
           }),
         }),
-      }),
+      })
     );
 
     const cycle3Var = cycle3.ast.set<VariableDeclaration>(
@@ -212,7 +205,7 @@ describe('test Key Path Expression V2', () => {
         initializer: createKeyPathExpression({
           keyPath: ['cycle2_var'],
         }),
-      }),
+      })
     );
 
     expect(cycle1Var.type.toJSON()).toEqual({

+ 74 - 50
packages/variable-engine/variable-core/src/ast/ast-node.ts

@@ -37,55 +37,72 @@ export interface ASTNodeRegistry<JSON extends ASTNodeJSON = any, InjectOpts = an
   new (params: CreateASTParams, injectOpts: InjectOpts): ASTNode<JSON>;
 }
 
+/**
+ * An `ASTNode` represents a fundamental unit of variable information within the system's Abstract Syntax Tree.
+ * It can model various constructs, for example:
+ * - **Declarations**: `const a = 1`
+ * - **Expressions**: `a.b.c`
+ * - **Types**: `number`, `string`, `boolean`
+ *
+ * Here is some characteristic of ASTNode:
+ * - **Tree-like Structure**: ASTNodes can be nested to form a tree, representing complex variable structures.
+ * - **Extendable**: New features can be added by extending the base ASTNode class.
+ * - **Reactive**: Changes in an ASTNode's value trigger events, enabling reactive programming patterns.
+ * - **Serializable**: ASTNodes can be converted to and from a JSON format (ASTNodeJSON) for storage or transmission.
+ */
 export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   implements Disposable
 {
   /**
    * @deprecated
-   * 获取 ASTNode 注入的 opts
+   * Get the injected options for the ASTNode.
    *
-   * 请使用 @injectToAst(XXXService) declare xxxService: XXXService 实现外部依赖注入
+   * Please use `@injectToAst(XXXService) declare xxxService: XXXService` to achieve external dependency injection.
    */
   public readonly opts?: InjectOpts;
 
   /**
-   * 节点的唯一标识符,节点不指定则默认由 nanoid 生成,不可更改
-   * - 如需要生成新 key,则销毁当前节点并生成新的节点
+   * The unique identifier of the ASTNode, which is **immutable**.
+   * - Immutable: Once assigned, the key cannot be changed.
+   * - Automatically generated if not specified, and cannot be changed as well.
+   * - If a new key needs to be generated, the current ASTNode should be destroyed and a new ASTNode should be generated.
    */
   public readonly key: Identifier;
 
   /**
-   * 节点类型
+   * The kind of the ASTNode.
    */
   static readonly kind: ASTKindType;
 
   /**
-   * 节点 Flags,记录一些 Flag 信息
+   * Node flags, used to record some flag information.
    */
   public readonly flags: number = ASTNodeFlags.None;
 
   /**
-   * 节点所处的作用域
+   * The scope in which the ASTNode is located.
    */
   public readonly scope: Scope;
 
   /**
-   * 父节点
+   * The parent ASTNode.
    */
   public readonly parent: ASTNode | undefined;
 
   /**
-   * 节点的版本号,每 fireChange 一次 version + 1
+   * The version number of the ASTNode, which increments by 1 each time `fireChange` is called.
    */
   protected _version: number = 0;
 
   /**
-   * 更新锁
+   * Update lock.
+   * - When set to `true`, `fireChange` will not trigger any events.
+   * - This is useful when multiple updates are needed, and you want to avoid multiple triggers.
    */
   public changeLocked = false;
 
   /**
-   * Batch Update 相关参数
+   * Parameters related to batch updates.
    */
   private _batch: {
     batching: boolean;
@@ -96,51 +113,52 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   };
 
   /**
-   * AST 节点变化事件,基于 Rxjs 实现
-   * - 使用了 BehaviorSubject, 在订阅时会自动触发一次事件,事件为当前值
+   * AST node change Observable events, implemented based on RxJS.
+   * - Emits the current ASTNode value upon subscription.
+   * - Emits a new value whenever `fireChange` is called.
    */
   public readonly value$: BehaviorSubject<ASTNode> = new BehaviorSubject<ASTNode>(this as ASTNode);
 
   /**
-   * 子节点
+   * Child ASTNodes.
    */
   protected _children = new Set<ASTNode>();
 
   /**
-   * 删除节点处理事件列表
+   * List of disposal handlers for the ASTNode.
    */
   public readonly toDispose: DisposableCollection = new DisposableCollection(
     Disposable.create(() => {
-      // 子元素删除时,父元素触发更新
+      // When a child element is deleted, the parent element triggers an update.
       this.parent?.fireChange();
       this.children.forEach((child) => child.dispose());
     })
   );
 
   /**
-   * 销毁时触发的回调
+   * Callback triggered upon disposal.
    */
   onDispose = this.toDispose.onDispose;
 
   /**
-   * 构造函数
-   * @param createParams 创建 ASTNode 的必要参数
-   * @param injectOptions 依赖注入各种模块
+   * Constructor.
+   * @param createParams Necessary parameters for creating an ASTNode.
+   * @param injectOptions Dependency injection for various modules.
    */
   constructor({ key, parent, scope }: CreateASTParams, opts?: InjectOpts) {
     this.scope = scope;
     this.parent = parent;
     this.opts = opts;
 
-    // 初始化 key 值,如果有传入 key 按照传入的来,否则按照 nanoid 随机生成
+    // Initialize the key value. If a key is passed in, use it; otherwise, generate a random one using nanoid.
     this.key = key || nanoid();
 
-    // 后续调用 fromJSON 内的所有 fireChange 合并成一个
+    // All `fireChange` calls within the subsequent `fromJSON` will be merged into one.
     this.fromJSON = this.withBatchUpdate(this.fromJSON.bind(this));
   }
 
   /**
-   * AST 节点的类型
+   * The type of the ASTNode.
    */
   get kind(): string {
     if (!(this.constructor as any).kind) {
@@ -150,24 +168,24 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 解析 AST JSON 数据
-   * @param json AST JSON 数据
+   * Parses AST JSON data.
+   * @param json AST JSON data.
    */
   abstract fromJSON(json: JSON): void;
 
   /**
-   * 获取当前节点所有子节点
+   * Gets all child ASTNodes of the current ASTNode.
    */
   get children(): ASTNode[] {
     return Array.from(this._children);
   }
 
   /**
-   * 转化为 ASTNodeJSON
+   * Serializes the current ASTNode to ASTNodeJSON.
    * @returns
    */
   toJSON(): ASTNodeJSON {
-    // 提示用户自己实现 ASTNode 的 toJSON,不要用兜底实现
+    // Prompt the user to implement the toJSON method for the ASTNode themselves, instead of using the fallback implementation.
     console.warn('[VariableEngine] Please Implement toJSON method for ' + this.kind);
 
     return {
@@ -176,8 +194,8 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 创建子节点
-   * @param json 子节点的 AST JSON
+   * Creates a child ASTNode.
+   * @param json The AST JSON of the child ASTNode.
    * @returns
    */
   protected createChildNode<ChildNode extends ASTNode = ASTNode>(json: ASTNodeJSON): ChildNode {
@@ -188,7 +206,7 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
       scope: this.scope,
     }) as ChildNode;
 
-    // 加入 _children 集合
+    // Add to the _children set.
     this._children.add(child);
     child.toDispose.push(
       Disposable.create(() => {
@@ -200,8 +218,8 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 更新子节点,快速实现子节点更新消费逻辑
-   * @param keyInThis 当前对象上的指定 key
+   * Updates a child ASTNode, quickly implementing the consumption logic for child ASTNode updates.
+   * @param keyInThis The specified key on the current object.
    */
   protected updateChildNodeByKey(keyInThis: keyof this, nextJSON?: ASTNodeJSON) {
     this.withBatchUpdate(updateChildNodeHelper).call(this, {
@@ -213,15 +231,15 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 批处理更新,批处理函数内所有的 fireChange 都合并成一个
-   * @param updater 批处理函数
+   * Batch updates the ASTNode, merging all `fireChange` calls within the batch function into one.
+   * @param updater The batch function.
    * @returns
    */
   protected withBatchUpdate<ParamTypes extends any[], ReturnType>(
     updater: (...args: ParamTypes) => ReturnType
   ) {
     return (...args: ParamTypes) => {
-      // batchUpdate 里面套 batchUpdate 只能生效一次
+      // Nested batchUpdate can only take effect once.
       if (this._batch.batching) {
         return updater.call(this, ...args);
       }
@@ -242,7 +260,7 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 触发当前节点更新
+   * Triggers an update for the current node.
    */
   fireChange(): void {
     if (this.changeLocked || this.disposed) {
@@ -261,24 +279,26 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 节点的版本值
-   * - 通过 NodeA === NodeB && versionA === versionB 可以比较两者是否相等
+   * The version value of the ASTNode.
+   * - You can used to check whether ASTNode are updated.
    */
   get version(): number {
     return this._version;
   }
 
   /**
-   * 节点唯一 hash 值
+   * The unique hash value of the ASTNode.
+   * - It will update when the ASTNode is updated.
+   * - You can used to check two ASTNode are equal.
    */
   get hash(): string {
     return `${this._version}${this.kind}${this.key}`;
   }
 
   /**
-   * 监听 AST 节点的变化
-   * @param observer 监听回调
-   * @param selector 监听指定数据
+   * Listens for changes to the ASTNode.
+   * @param observer The listener callback.
+   * @param selector Listens for specified data.
    * @returns
    */
   subscribe<Data = this>(
@@ -293,21 +313,25 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
             (a, b) => shallowEqual(a, b),
             (value) => {
               if (value instanceof ASTNode) {
-                // 如果 value 是 ASTNode,则进行 hash 的比较
+                // If the value is an ASTNode, compare its hash.
                 return value.hash;
               }
               return value;
             }
           ),
-          // 默认跳过 BehaviorSubject 第一次触发
+          // By default, skip the first trigger of BehaviorSubject.
           triggerOnInit ? tap(() => null) : skip(1),
-          // 每个 animationFrame 内所有更新合并成一个
+          // All updates within each animationFrame are merged into one.
           debounceAnimation ? debounceTime(0, animationFrameScheduler) : tap(() => null)
         )
         .subscribe(observer)
     );
   }
 
+  /**
+   * Dispatches a global event for the current ASTNode.
+   * @param event The global event.
+   */
   dispatchGlobalEvent<ActionType extends GlobalEventActionType = GlobalEventActionType>(
     event: Omit<ActionType, 'ast'>
   ) {
@@ -318,10 +342,10 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 销毁
+   * Disposes the ASTNode.
    */
   dispose(): void {
-    // 防止销毁多次
+    // Prevent multiple disposals.
     if (this.toDispose.disposed) {
       return;
     }
@@ -329,7 +353,7 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
     this.toDispose.dispose();
     this.dispatchGlobalEvent<DisposeASTAction>({ type: 'DisposeAST' });
 
-    // complete 事件发出时,需要确保当前 ASTNode 为 disposed 状态
+    // When the complete event is emitted, ensure that the current ASTNode is in a disposed state.
     this.value$.complete();
     this.value$.unsubscribe();
   }
@@ -339,7 +363,7 @@ export abstract class ASTNode<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 节点扩展信息
+   * Extended information of the ASTNode.
    */
   [key: string]: unknown;
 }

+ 11 - 14
packages/variable-engine/variable-core/src/ast/ast-registers.ts

@@ -18,18 +18,16 @@ import {
   ObjectType,
   StringType,
 } from './type';
-import {
-  EnumerateExpression,
-  // KeyPathExpression,
-  KeyPathExpressionV2,
-  WrapArrayExpression,
-} from './expression';
+import { EnumerateExpression, KeyPathExpression, WrapArrayExpression } from './expression';
 import { Property, VariableDeclaration, VariableDeclarationList } from './declaration';
 import { DataNode, MapNode } from './common';
 import { ASTNode, ASTNodeRegistry } from './ast-node';
 
 type DataInjector = () => Record<string, any>;
 
+/**
+ * Register the AST node to the engine.
+ */
 @injectable()
 export class ASTRegisters {
   protected injectors: Map<ASTKindType, DataInjector> = new Map();
@@ -37,7 +35,7 @@ export class ASTRegisters {
   protected astMap: Map<ASTKindType, ASTNodeRegistry> = new Map();
 
   /**
-   * 核心 AST 节点注册
+   * Core AST node registration.
    */
   constructor() {
     this.registerAST(StringType);
@@ -51,8 +49,7 @@ export class ASTRegisters {
     this.registerAST(Property);
     this.registerAST(VariableDeclaration);
     this.registerAST(VariableDeclarationList);
-    // this.registerAST(KeyPathExpression);
-    this.registerAST(KeyPathExpressionV2);
+    this.registerAST(KeyPathExpression);
 
     this.registerAST(EnumerateExpression);
     this.registerAST(WrapArrayExpression);
@@ -61,8 +58,8 @@ export class ASTRegisters {
   }
 
   /**
-   * 创建 AST 节点
-   * @param param 创建参数
+   * Creates an AST node.
+   * @param param Creation parameters.
    * @returns
    */
   createAST<ReturnNode extends ASTNode = ASTNode>(
@@ -86,7 +83,7 @@ export class ASTRegisters {
       injector?.() || {}
     ) as ReturnNode;
 
-    // 初始化创建不触发 fireChange
+    // Do not trigger fireChange during initial creation.
     node.changeLocked = true;
     node.fromJSON(omit(json, ['key', 'kind']));
     node.changeLocked = false;
@@ -102,7 +99,7 @@ export class ASTRegisters {
   }
 
   /**
-   * 根据 AST 节点类型获取节点 Registry
+   * Gets the node Registry by AST node type.
    * @param kind
    * @returns
    */
@@ -111,7 +108,7 @@ export class ASTRegisters {
   }
 
   /**
-   * 注册 AST 节点
+   * Registers an AST node.
    * @param ASTNode
    * @param injector
    */

+ 16 - 1
packages/variable-engine/variable-core/src/ast/common/data-node.ts

@@ -9,17 +9,24 @@ import { ASTKind, ASTNodeJSON } from '../types';
 import { ASTNode } from '../ast-node';
 
 /**
- * 通用数据 AST 节点,无子节点
+ * Represents a general data node with no child nodes.
  */
 export class DataNode<Data = any> extends ASTNode {
   static kind: string = ASTKind.DataNode;
 
   protected _data: Data;
 
+  /**
+   * The data of the node.
+   */
   get data(): Data {
     return this._data;
   }
 
+  /**
+   * Deserializes the `DataNodeJSON` to the `DataNode`.
+   * @param json The `DataNodeJSON` to deserialize.
+   */
   fromJSON(json: Data): void {
     const { kind, ...restData } = json as ASTNodeJSON;
 
@@ -29,6 +36,10 @@ export class DataNode<Data = any> extends ASTNode {
     }
   }
 
+  /**
+   * Serialize the `DataNode` to `DataNodeJSON`.
+   * @returns The JSON representation of `DataNode`.
+   */
   toJSON() {
     return {
       kind: ASTKind.DataNode,
@@ -36,6 +47,10 @@ export class DataNode<Data = any> extends ASTNode {
     };
   }
 
+  /**
+   * Partially update the data of the node.
+   * @param nextData The data to be updated.
+   */
   partialUpdate(nextData: Data) {
     if (!shallowEqual(nextData, this._data)) {
       this._data = {

+ 24 - 4
packages/variable-engine/variable-core/src/ast/common/list-node.ts

@@ -6,27 +6,43 @@
 import { ASTKind, ASTNodeJSON } from '../types';
 import { ASTNode } from '../ast-node';
 
+/**
+ * ASTNodeJSON representation of `ListNode`
+ */
 export interface ListNodeJSON {
+  /**
+   * The list of nodes.
+   */
   list: ASTNodeJSON[];
 }
 
+/**
+ * Represents a list of nodes.
+ */
 export class ListNode extends ASTNode<ListNodeJSON> {
   static kind: string = ASTKind.ListNode;
 
   protected _list: ASTNode[];
 
+  /**
+   * The list of nodes.
+   */
   get list(): ASTNode[] {
     return this._list;
   }
 
+  /**
+   * Deserializes the `ListNodeJSON` to the `ListNode`.
+   * @param json The `ListNodeJSON` to deserialize.
+   */
   fromJSON({ list }: ListNodeJSON): void {
-    // 超出长度的 children 需要被销毁
-    this._list.slice(list.length).forEach(_item => {
+    // Children that exceed the length need to be destroyed.
+    this._list.slice(list.length).forEach((_item) => {
       _item.dispose();
       this.fireChange();
     });
 
-    // 剩余 children 的处理
+    // Processing of remaining children.
     this._list = list.map((_item, idx) => {
       const prevItem = this._list[idx];
 
@@ -41,10 +57,14 @@ export class ListNode extends ASTNode<ListNodeJSON> {
     });
   }
 
+  /**
+   * Serialize the `ListNode` to `ListNodeJSON`.
+   * @returns The JSON representation of `ListNode`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.ListNode,
-      list: this._list.map(item => item.toJSON()),
+      list: this._list.map((item) => item.toJSON()),
     };
   }
 }

+ 26 - 8
packages/variable-engine/variable-core/src/ast/common/map-node.ts

@@ -7,15 +7,28 @@ import { updateChildNodeHelper } from '../utils/helpers';
 import { ASTKind, ASTNodeJSON } from '../types';
 import { ASTNode } from '../ast-node';
 
+/**
+ * ASTNodeJSON representation of `MapNode`
+ */
 export interface MapNodeJSON {
+  /**
+   * The map of nodes.
+   */
   map: [string, ASTNodeJSON][];
 }
 
+/**
+ * Represents a map of nodes.
+ */
 export class MapNode extends ASTNode<MapNodeJSON> {
   static kind: string = ASTKind.MapNode;
 
   protected map: Map<string, ASTNode> = new Map<string, ASTNode>();
 
+  /**
+   * Deserializes the `MapNodeJSON` to the `MapNode`.
+   * @param json The `MapNodeJSON` to deserialize.
+   */
   fromJSON({ map }: MapNodeJSON): void {
     const removedKeys = new Set(this.map.keys());
 
@@ -29,6 +42,10 @@ export class MapNode extends ASTNode<MapNodeJSON> {
     }
   }
 
+  /**
+   * Serialize the `MapNode` to `MapNodeJSON`.
+   * @returns The JSON representation of `MapNode`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.MapNode,
@@ -37,9 +54,10 @@ export class MapNode extends ASTNode<MapNodeJSON> {
   }
 
   /**
-   * 往 Map 中设置 ASTNode
-   * @param key ASTNode 的索引,
-   * @param json
+   * Set a node in the map.
+   * @param key The key of the node.
+   * @param nextJSON The JSON representation of the node.
+   * @returns The node instance.
    */
   set<Node extends ASTNode = ASTNode>(key: string, nextJSON: ASTNodeJSON): Node {
     return this.withBatchUpdate(updateChildNodeHelper).call(this, {
@@ -51,8 +69,8 @@ export class MapNode extends ASTNode<MapNodeJSON> {
   }
 
   /**
-   * 移除指定 ASTNode
-   * @param key
+   * Remove a node from the map.
+   * @param key The key of the node.
    */
   remove(key: string) {
     this.get(key)?.dispose();
@@ -61,9 +79,9 @@ export class MapNode extends ASTNode<MapNodeJSON> {
   }
 
   /**
-   * 获取 ASTNode
-   * @param key
-   * @returns
+   * Get a node from the map.
+   * @param key The key of the node.
+   * @returns The node instance if found, otherwise `undefined`.
    */
   get<Node extends ASTNode = ASTNode>(key: string): Node | undefined {
     return this.map.get(key) as Node | undefined;

+ 65 - 8
packages/variable-engine/variable-core/src/ast/declaration/base-variable-field.ts

@@ -13,15 +13,39 @@ import { type BaseExpression } from '../expression';
 import { ASTNode } from '../ast-node';
 
 /**
- * 声明类 AST 节点
+ * ASTNodeJSON representation of `BaseVariableField`
  */
 export type BaseVariableFieldJSON<VariableMeta = any> = {
+  /**
+   * key of the variable field
+   * - For `VariableDeclaration`, the key should be global unique.
+   * - For `Property`, the key is the property name.
+   */
   key?: Identifier;
+  /**
+   * type of the variable field, similar to js code:
+   * `const v: string`
+   */
   type?: ASTNodeJSONOrKind;
-  initializer?: ASTNodeJSON; // 变量初始化表达式
+  /**
+   * initializer of the variable field, similar to js code:
+   * `const v = 'hello'`
+   *
+   * with initializer, the type of field will be inferred from the initializer.
+   */
+  initializer?: ASTNodeJSON;
+  /**
+   * meta data of the variable field, you cans store information like `title`, `icon`, etc.
+   */
   meta?: VariableMeta;
 };
 
+/**
+ * Variable Field abstract class, which is the base class for `VariableDeclaration` and `Property`
+ *
+ * - `VariableDeclaration` is used to declare a variable in a block scope.
+ * - `Property` is used to declare a property in an object.
+ */
 export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
   BaseVariableFieldJSON<VariableMeta>
 > {
@@ -34,34 +58,54 @@ export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
   protected _initializer?: BaseExpression;
 
   /**
-   * 父变量字段,通过由近而远的方式进行排序
+   * Parent variable fields, sorted from closest to farthest
    */
   get parentFields(): BaseVariableField[] {
     return getParentFields(this);
   }
 
+  /**
+   * KeyPath of the variable field, sorted from farthest to closest
+   */
   get keyPath(): string[] {
     return [...this.parentFields.reverse().map((_field) => _field.key), this.key];
   }
 
+  /**
+   * Metadata of the variable field, you cans store information like `title`, `icon`, etc.
+   */
   get meta(): VariableMeta {
     return this._meta;
   }
 
+  /**
+   * Type of the variable field, similar to js code:
+   * `const v: string`
+   */
   get type(): BaseType {
     return (this._initializer?.returnType || this._type)!;
   }
 
+  /**
+   * Initializer of the variable field, similar to js code:
+   * `const v = 'hello'`
+   *
+   * with initializer, the type of field will be inferred from the initializer.
+   */
   get initializer(): BaseExpression | undefined {
     return this._initializer;
   }
 
+  /**
+   * The global unique hash of the field, and will be changed when the field is updated.
+   */
   get hash(): string {
     return `[${this._version}]${this.keyPath.join('.')}`;
   }
 
   /**
-   * 解析 VariableDeclarationJSON 从而生成变量声明节点
+   * Deserialize the `BaseVariableFieldJSON` to the `BaseVariableField`.
+   * @param json ASTJSON representation of `BaseVariableField`
    */
   fromJSON({ type, initializer, meta }: BaseVariableFieldJSON<VariableMeta>): void {
     // 类型变化
@@ -74,15 +118,27 @@ export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
     this.updateMeta(meta!);
   }
 
+  /**
+   * Update the type of the variable field
+   * @param type type ASTJSON representation of Type
+   */
   updateType(type: BaseVariableFieldJSON['type']) {
     const nextTypeJson = typeof type === 'string' ? { kind: type } : type;
     this.updateChildNodeByKey('_type', nextTypeJson);
   }
 
+  /**
+   * Update the initializer of the variable field
+   * @param nextInitializer initializer ASTJSON representation of Expression
+   */
   updateInitializer(nextInitializer?: BaseVariableFieldJSON['initializer']) {
     this.updateChildNodeByKey('_initializer', nextInitializer);
   }
 
+  /**
+   * Update the meta data of the variable field
+   * @param nextMeta meta data of the variable field
+   */
   updateMeta(nextMeta: VariableMeta) {
     if (!shallowEqual(nextMeta, this._meta)) {
       this._meta = nextMeta;
@@ -91,7 +147,8 @@ export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
   }
 
   /**
-   * 根据 keyPath 去找下钻的变量字段
+   * Get the variable field by keyPath, similar to js code:
+   * `v.a.b`
    * @param keyPath
    * @returns
    */
@@ -104,7 +161,7 @@ export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
   }
 
   /**
-   * 监听类型变化
+   * Subscribe to type change of the variable field
    * @param observer
    * @returns
    */
@@ -113,8 +170,8 @@ export abstract class BaseVariableField<VariableMeta = any> extends ASTNode<
   }
 
   /**
-   * 转换为 JSON
-   * @returns
+   * Serialize the variable field to JSON
+   * @returns ASTNodeJSON representation of `BaseVariableField`
    */
   toJSON(): BaseVariableFieldJSON<VariableMeta> & { kind: string } {
     return {

+ 7 - 1
packages/variable-engine/variable-core/src/ast/declaration/property.ts

@@ -6,11 +6,17 @@
 import { ASTKind } from '../types';
 import { BaseVariableField, BaseVariableFieldJSON } from './base-variable-field';
 
+/**
+ * ASTNodeJSON representation of the `Property`.
+ */
 export type PropertyJSON<VariableMeta = any> = BaseVariableFieldJSON<VariableMeta> & {
-  // Key 为必填项
+  // Key is a required field.
   key: string;
 };
 
+/**
+ * `Property` is a variable field that represents a property of a `ObjectType`.
+ */
 export class Property<VariableMeta = any> extends BaseVariableField<VariableMeta> {
   static kind: string = ASTKind.Property;
 }

+ 28 - 8
packages/variable-engine/variable-core/src/ast/declaration/variable-declaration-list.ts

@@ -10,10 +10,13 @@ import { type VariableDeclarationJSON, VariableDeclaration } from './variable-de
 
 export interface VariableDeclarationListJSON<VariableMeta = any> {
   /**
-   *  declarations 一定是 VariableDeclaration 类型,因此业务可以不用填 kind
+   * `declarations` must be of type `VariableDeclaration`, so the business can omit the `kind` field.
    */
   declarations?: VariableDeclarationJSON<VariableMeta>[];
-  startOrder?: number; // 变量起始的排序序号
+  /**
+   * The starting order number for variables.
+   */
+  startOrder?: number;
 }
 
 export type VariableDeclarationListChangeAction = GlobalEventActionType<
@@ -28,20 +31,33 @@ export type VariableDeclarationListChangeAction = GlobalEventActionType<
 export class VariableDeclarationList extends ASTNode<VariableDeclarationListJSON> {
   static kind: string = ASTKind.VariableDeclarationList;
 
+  /**
+   * Map of variable declarations, keyed by variable name.
+   */
   declarationTable: Map<string, VariableDeclaration> = new Map();
 
+  /**
+   * Variable declarations, sorted by `order`.
+   */
   declarations: VariableDeclaration[];
 
+  /**
+   * Deserialize the `VariableDeclarationListJSON` to the `VariableDeclarationList`.
+   * - VariableDeclarationListChangeAction will be dispatched after deserialization.
+   *
+   * @param declarations Variable declarations.
+   * @param startOrder The starting order number for variables. Default is 0.
+   */
   fromJSON({ declarations, startOrder }: VariableDeclarationListJSON): void {
     const removedKeys = new Set(this.declarationTable.keys());
     const prev = [...(this.declarations || [])];
 
-    // 遍历新的 properties
+    // Iterate over the new properties.
     this.declarations = (declarations || []).map(
       (declaration: VariableDeclarationJSON, idx: number) => {
         const order = (startOrder || 0) + idx;
 
-        // 如果没有设置 key,则复用上次的 key
+        // If the key is not set, reuse the previous key.
         const declarationKey = declaration.key || this.declarations?.[idx]?.key;
         const existDeclaration = this.declarationTable.get(declarationKey);
         if (declarationKey) {
@@ -64,11 +80,11 @@ export class VariableDeclarationList extends ASTNode<VariableDeclarationListJSON
 
           return newDeclaration;
         }
-      },
+      }
     );
 
-    // 删除没有出现过的变量
-    removedKeys.forEach(key => {
+    // Delete variables that no longer exist.
+    removedKeys.forEach((key) => {
       const declaration = this.declarationTable.get(key);
       declaration?.dispose();
       this.declarationTable.delete(key);
@@ -83,10 +99,14 @@ export class VariableDeclarationList extends ASTNode<VariableDeclarationListJSON
     });
   }
 
+  /**
+   * Serialize the `VariableDeclarationList` to the `VariableDeclarationListJSON`.
+   * @returns ASTJSON representation of `VariableDeclarationList`
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.VariableDeclarationList,
-      properties: this.declarations.map(_declaration => _declaration.toJSON()),
+      declarations: this.declarations.map((_declaration) => _declaration.toJSON()),
     };
   }
 }

+ 21 - 11
packages/variable-engine/variable-core/src/ast/declaration/variable-declaration.ts

@@ -4,23 +4,34 @@
  */
 
 import { ASTKind, GlobalEventActionType, type CreateASTParams } from '../types';
-import { ASTNode } from '../ast-node';
 import { BaseVariableField, BaseVariableFieldJSON } from './base-variable-field';
 
 /**
- * 声明类 AST 节点
+ * ASTNodeJSON representation of the `VariableDeclaration`.
  */
 export type VariableDeclarationJSON<VariableMeta = any> = BaseVariableFieldJSON<VariableMeta> & {
-  order?: number; // 变量排序
+  /**
+   * Variable sorting order, which is used to sort variables in `scope.outputs.variables`
+   */
+  order?: number;
 };
 
+/**
+ * Action type for re-sorting variable declarations.
+ */
 export type ReSortVariableDeclarationsAction = GlobalEventActionType<'ReSortVariableDeclarations'>;
 
+/**
+ * `VariableDeclaration` is a variable field that represents a variable declaration.
+ */
 export class VariableDeclaration<VariableMeta = any> extends BaseVariableField<VariableMeta> {
   static kind: string = ASTKind.VariableDeclaration;
 
   protected _order: number = 0;
 
+  /**
+   * Variable sorting order, which is used to sort variables in `scope.outputs.variables`
+   */
   get order(): number {
     return this._order;
   }
@@ -30,16 +41,20 @@ export class VariableDeclaration<VariableMeta = any> extends BaseVariableField<V
   }
 
   /**
-   * 解析 VariableDeclarationJSON 从而生成变量声明节点
+   * Deserialize the `VariableDeclarationJSON` to the `VariableDeclaration`.
    */
   fromJSON({ order, ...rest }: VariableDeclarationJSON<VariableMeta>): void {
-    // 更新排序
+    // Update order.
     this.updateOrder(order);
 
-    // 更新其他信息
+    // Update other information.
     super.fromJSON(rest as BaseVariableFieldJSON<VariableMeta>);
   }
 
+  /**
+   * Update the sorting order of the variable declaration.
+   * @param order Variable sorting order. Default is 0.
+   */
   updateOrder(order: number = 0): void {
     if (order !== this._order) {
       this._order = order;
@@ -49,9 +64,4 @@ export class VariableDeclaration<VariableMeta = any> extends BaseVariableField<V
       this.fireChange();
     }
   }
-
-  // 监听类型变化
-  onTypeChange(observer: (type: ASTNode | undefined) => void) {
-    return this.subscribe(observer, { selector: (curr) => curr.type });
-  }
 }

+ 18 - 9
packages/variable-engine/variable-core/src/ast/expression/base-expression.ts

@@ -26,6 +26,11 @@ import { IVariableTable } from '../../scope/types';
 
 type ExpressionRefs = (BaseVariableField | undefined)[];
 
+/**
+ * Base class for all expressions.
+ *
+ * All other expressions should extend this class.
+ */
 export abstract class BaseExpression<
   JSON extends ASTNodeJSON = any,
   InjectOpts = any
@@ -33,35 +38,40 @@ export abstract class BaseExpression<
   public flags: ASTNodeFlags = ASTNodeFlags.Expression;
 
   /**
-   * 获取全局变量表,方便表达式获取引用变量
+   * Get the global variable table, which is used to access referenced variables.
    */
   get globalVariableTable(): IVariableTable {
     return this.scope.variableEngine.globalVariableTable;
   }
 
   /**
-   * 父变量字段,通过由近而远的方式进行排序
+   * Parent variable fields, sorted from closest to farthest.
    */
   get parentFields(): BaseVariableField[] {
     return getParentFields(this);
   }
 
   /**
-   * 获取表达式引用的变量字段
-   * - 通常是 变量 VariableDeclaration,或者 属性 Property 节点
+   * Get the variable fields referenced by the expression.
+   *
+   * This method should be implemented by subclasses.
+   * @returns An array of referenced variable fields.
    */
   abstract getRefFields(): ExpressionRefs;
 
   /**
-   * 表达式返回的数据类型
+   * The return type of the expression.
    */
   abstract returnType: BaseType | undefined;
 
   /**
-   * 引用变量
+   * The variable fields referenced by the expression.
    */
   protected _refs: ExpressionRefs = [];
 
+  /**
+   * The variable fields referenced by the expression.
+   */
   get refs(): ExpressionRefs {
     return this._refs;
   }
@@ -69,15 +79,14 @@ export abstract class BaseExpression<
   protected refreshRefs$: Subject<void> = new Subject();
 
   /**
-   * 刷新变量引用
+   * Refresh the variable references.
    */
   refreshRefs() {
     this.refreshRefs$.next();
   }
 
   /**
-   * 监听引用变量变化
-   * 监听 [a.b.c] -> [a.b]
+   * An observable that emits the referenced variable fields when they change.
    */
   refs$: Observable<ExpressionRefs> = this.refreshRefs$.pipe(
     map(() => this.getRefFields()),

+ 28 - 4
packages/variable-engine/variable-core/src/ast/expression/enumerate-expression.ts

@@ -8,42 +8,66 @@ import { ArrayType } from '../type/array';
 import { BaseType } from '../type';
 import { BaseExpression } from './base-expression';
 
+/**
+ * ASTNodeJSON representation of `EnumerateExpression`
+ */
 export interface EnumerateExpressionJSON {
-  enumerateFor: ASTNodeJSON; // 需要被遍历的表达式类型
+  /**
+   * The expression to be enumerated.
+   */
+  enumerateFor: ASTNodeJSON;
 }
 
 /**
- * 遍历表达式,对列表进行遍历,获取遍历后的变量类型
+ * Represents an enumeration expression, which iterates over a list and returns the type of the enumerated variable.
  */
 export class EnumerateExpression extends BaseExpression<EnumerateExpressionJSON> {
   static kind: string = ASTKind.EnumerateExpression;
 
   protected _enumerateFor: BaseExpression | undefined;
 
+  /**
+   * The expression to be enumerated.
+   */
   get enumerateFor() {
     return this._enumerateFor;
   }
 
+  /**
+   * The return type of the expression.
+   */
   get returnType(): BaseType | undefined {
-    // 被遍历表达式的返回值
+    // The return value of the enumerated expression.
     const childReturnType = this.enumerateFor?.returnType;
 
     if (childReturnType?.kind === ASTKind.Array) {
-      // 获取 Array 的 Item 类型
+      // Get the item type of the array.
       return (childReturnType as ArrayType).items;
     }
 
     return undefined;
   }
 
+  /**
+   * Get the variable fields referenced by the expression.
+   * @returns An empty array, as this expression does not reference any variables.
+   */
   getRefFields(): [] {
     return [];
   }
 
+  /**
+   * Deserializes the `EnumerateExpressionJSON` to the `EnumerateExpression`.
+   * @param json The `EnumerateExpressionJSON` to deserialize.
+   */
   fromJSON({ enumerateFor: expression }: EnumerateExpressionJSON): void {
     this.updateChildNodeByKey('_enumerateFor', expression);
   }
 
+  /**
+   * Serialize the `EnumerateExpression` to `EnumerateExpressionJSON`.
+   * @returns The JSON representation of `EnumerateExpression`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.EnumerateExpression,

+ 2 - 2
packages/variable-engine/variable-core/src/ast/expression/index.ts

@@ -4,7 +4,7 @@
  */
 
 export { BaseExpression } from './base-expression';
-export { KeyPathExpression, type KeyPathExpressionJSON } from './keypath-expression';
 export { EnumerateExpression, type EnumerateExpressionJSON } from './enumerate-expression';
-export { KeyPathExpressionV2 } from './keypath-expression-v2';
+export { KeyPathExpression, type KeyPathExpressionJSON } from './keypath-expression';
+export { LegacyKeyPathExpression } from './legacy-keypath-expression';
 export { WrapArrayExpression, type WrapArrayExpressionJSON } from './wrap-array-expression';

+ 0 - 120
packages/variable-engine/variable-core/src/ast/expression/keypath-expression-v2.ts

@@ -1,120 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import { shallowEqual } from 'fast-equals';
-
-import { checkRefCycle } from '../utils/expression';
-import { ASTNodeJSON, ASTKind, CreateASTParams } from '../types';
-import { BaseType } from '../type';
-import { type BaseVariableField } from '../declaration';
-import { subsToDisposable } from '../../utils/toDisposable';
-import { BaseExpression } from './base-expression';
-
-interface KeyPathExpressionJSON {
-  keyPath: string[];
-}
-
-/**
- * 新版 KeyPathExpressionV2,相比旧版:
- * - returnType 拷贝新一份,避免引用问题
- * - 引入成环检测
- */
-export class KeyPathExpressionV2<
-  CustomPathJSON extends ASTNodeJSON = KeyPathExpressionJSON,
-> extends BaseExpression<CustomPathJSON> {
-  static kind: string = ASTKind.KeyPathExpression;
-
-  protected _keyPath: string[] = [];
-
-  get keyPath(): string[] {
-    return this._keyPath;
-  }
-
-  getRefFields(): BaseVariableField[] {
-    const ref = this.scope.available.getByKeyPath(this._keyPath);
-
-    // 刷新引用时,检测循环引用,如果存在循环引用则不引用该变量
-    if (checkRefCycle(this, [ref])) {
-      // 提示存在循环引用
-      console.warn(
-        '[CustomKeyPathExpression] checkRefCycle: Reference Cycle Existed',
-        this.parentFields.map(_field => _field.key).reverse(),
-      );
-      return [];
-    }
-
-    return ref ? [ref] : [];
-  }
-
-  // 直接生成新的 returnType 节点而不是直接复用
-  // 确保不同的 keyPath 不指向同一个 Field
-  _returnType: BaseType;
-
-  get returnType() {
-    return this._returnType;
-  }
-
-  /**
-   * 业务重改该方法可快速定制自己的 Path 表达式
-   * - 只需要将业务的 Path 解析为变量系统的 KeyPath 即可
-   * @param json 业务定义的 Path 表达式
-   * @returns
-   */
-  protected parseToKeyPath(json: CustomPathJSON): string[] {
-    // 默认 JSON 为 KeyPathExpressionJSON 格式
-    return (json as unknown as KeyPathExpressionJSON).keyPath;
-  }
-
-  fromJSON(json: CustomPathJSON): void {
-    const keyPath = this.parseToKeyPath(json);
-
-    if (!shallowEqual(keyPath, this._keyPath)) {
-      this._keyPath = keyPath;
-
-      // keyPath 更新后,需刷新引用变量
-      this.refreshRefs();
-    }
-  }
-
-  getReturnTypeJSONByRef(_ref: BaseVariableField | undefined): ASTNodeJSON | undefined {
-    return _ref?.type?.toJSON();
-  }
-
-  protected prevRefTypeHash: string | undefined;
-
-  constructor(params: CreateASTParams, opts: any) {
-    super(params, opts);
-
-    this.toDispose.pushAll([
-      // 可以用变量列表变化时候 (有新增或者删除时)
-      this.scope.available.onVariableListChange(() => {
-        this.refreshRefs();
-      }),
-      // this._keyPath 指向的可引用变量发生变化时,刷新引用数据
-      this.scope.available.onAnyVariableChange(_v => {
-        if (_v.key === this._keyPath[0]) {
-          this.refreshRefs();
-        }
-      }),
-      subsToDisposable(
-        this.refs$.subscribe(_type => {
-          const [ref] = this._refs;
-
-          if (this.prevRefTypeHash !== ref?.type?.hash) {
-            this.prevRefTypeHash = ref?.type?.hash;
-            this.updateChildNodeByKey('_returnType', this.getReturnTypeJSONByRef(ref));
-          }
-        }),
-      ),
-    ]);
-  }
-
-  toJSON(): ASTNodeJSON {
-    return {
-      kind: ASTKind.KeyPathExpression,
-      keyPath: this._keyPath,
-    };
-  }
-}

+ 84 - 19
packages/variable-engine/variable-core/src/ast/expression/keypath-expression.ts

@@ -5,82 +5,147 @@
 
 import { shallowEqual } from 'fast-equals';
 
+import { checkRefCycle } from '../utils/expression';
 import { ASTNodeJSON, ASTKind, CreateASTParams } from '../types';
 import { BaseType } from '../type';
-import { ASTNodeFlags } from '../flags';
 import { type BaseVariableField } from '../declaration';
+import { subsToDisposable } from '../../utils/toDisposable';
 import { BaseExpression } from './base-expression';
 
+/**
+ * ASTNodeJSON representation of `KeyPathExpression`
+ */
 export interface KeyPathExpressionJSON {
+  /**
+   * The key path of the variable.
+   */
   keyPath: string[];
 }
 
+/**
+ * Represents a key path expression, which is used to reference a variable by its key path.
+ *
+ * This is the V2 of `KeyPathExpression`, with the following improvements:
+ * - `returnType` is copied to a new instance to avoid reference issues.
+ * - Circular reference detection is introduced.
+ */
 export class KeyPathExpression<
-  CustomPathJSON extends ASTNodeJSON = KeyPathExpressionJSON,
+  CustomPathJSON extends ASTNodeJSON = KeyPathExpressionJSON
 > extends BaseExpression<CustomPathJSON> {
   static kind: string = ASTKind.KeyPathExpression;
 
   protected _keyPath: string[] = [];
 
+  /**
+   * The key path of the variable.
+   */
   get keyPath(): string[] {
     return this._keyPath;
   }
 
+  /**
+   * Get the variable fields referenced by the expression.
+   * @returns An array of referenced variable fields.
+   */
   getRefFields(): BaseVariableField[] {
     const ref = this.scope.available.getByKeyPath(this._keyPath);
+
+    // When refreshing references, check for circular references. If a circular reference exists, do not reference the variable.
+    if (checkRefCycle(this, [ref])) {
+      // Prompt that a circular reference exists.
+      console.warn(
+        '[CustomKeyPathExpression] checkRefCycle: Reference Cycle Existed',
+        this.parentFields.map((_field) => _field.key).reverse()
+      );
+      return [];
+    }
+
     return ref ? [ref] : [];
   }
 
-  get returnType(): BaseType | undefined {
-    const [refNode] = this._refs || [];
-
-    // 获取引用变量的类型
-    if (refNode && refNode.flags & ASTNodeFlags.VariableField) {
-      return refNode.type;
-    }
+  /**
+   * The return type of the expression.
+   *
+   * A new `returnType` node is generated directly, instead of reusing the existing one, to ensure that different key paths do not point to the same field.
+   */
+  _returnType: BaseType;
 
-    return;
+  /**
+   * The return type of the expression.
+   */
+  get returnType() {
+    return this._returnType;
   }
 
   /**
-   * 业务重改该方法可快速定制自己的 Path 表达式
-   * - 只需要将业务的 Path 解析为变量系统的 KeyPath 即可
-   * @param json 业务定义的 Path 表达式
-   * @returns
+   * Parse the business-defined path expression into a key path.
+   *
+   * Businesses can quickly customize their own path expressions by modifying this method.
+   * @param json The path expression defined by the business.
+   * @returns The key path.
    */
   protected parseToKeyPath(json: CustomPathJSON): string[] {
-    // 默认 JSON 为 KeyPathExpressionJSON 格式
+    // The default JSON is in KeyPathExpressionJSON format.
     return (json as unknown as KeyPathExpressionJSON).keyPath;
   }
 
+  /**
+   * Deserializes the `KeyPathExpressionJSON` to the `KeyPathExpression`.
+   * @param json The `KeyPathExpressionJSON` to deserialize.
+   */
   fromJSON(json: CustomPathJSON): void {
     const keyPath = this.parseToKeyPath(json);
 
     if (!shallowEqual(keyPath, this._keyPath)) {
       this._keyPath = keyPath;
 
-      // keyPath 更新后,需刷新引用变量
+      // After the keyPath is updated, the referenced variables need to be refreshed.
       this.refreshRefs();
     }
   }
 
+  /**
+   * Get the return type JSON by reference.
+   * @param _ref The referenced variable field.
+   * @returns The JSON representation of the return type.
+   */
+  getReturnTypeJSONByRef(_ref: BaseVariableField | undefined): ASTNodeJSON | undefined {
+    return _ref?.type?.toJSON();
+  }
+
+  protected prevRefTypeHash: string | undefined;
+
   constructor(params: CreateASTParams, opts: any) {
     super(params, opts);
 
     this.toDispose.pushAll([
-      // 可以用变量列表变化时候 (有新增或者删除时)
+      // Can be used when the variable list changes (when there are additions or deletions).
       this.scope.available.onVariableListChange(() => {
         this.refreshRefs();
       }),
-      // this._keyPath 指向的可引用变量发生变化时,刷新引用数据
-      this.scope.available.onAnyVariableChange(_v => {
+      // When the referable variable pointed to by this._keyPath changes, refresh the reference data.
+      this.scope.available.onAnyVariableChange((_v) => {
         if (_v.key === this._keyPath[0]) {
           this.refreshRefs();
         }
       }),
+      subsToDisposable(
+        this.refs$.subscribe((_type) => {
+          const [ref] = this._refs;
+
+          if (this.prevRefTypeHash !== ref?.type?.hash) {
+            this.prevRefTypeHash = ref?.type?.hash;
+            this.updateChildNodeByKey('_returnType', this.getReturnTypeJSONByRef(ref));
+          }
+        })
+      ),
     ]);
   }
 
+  /**
+   * Serialize the `KeyPathExpression` to `KeyPathExpressionJSON`.
+   * @returns The JSON representation of `KeyPathExpression`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.KeyPathExpression,

+ 119 - 0
packages/variable-engine/variable-core/src/ast/expression/legacy-keypath-expression.ts

@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { shallowEqual } from 'fast-equals';
+
+import { ASTNodeJSON, ASTKind, CreateASTParams } from '../types';
+import { BaseType } from '../type';
+import { ASTNodeFlags } from '../flags';
+import { type BaseVariableField } from '../declaration';
+import { BaseExpression } from './base-expression';
+
+/**
+ * ASTNodeJSON representation of `KeyPathExpression`
+ */
+export interface KeyPathExpressionJSON {
+  /**
+   * The key path of the variable.
+   */
+  keyPath: string[];
+}
+
+/**
+ * @deprecated Use `KeyPathExpression` instead.
+ * Represents a key path expression, which is used to reference a variable by its key path.
+ */
+export class LegacyKeyPathExpression<
+  CustomPathJSON extends ASTNodeJSON = KeyPathExpressionJSON
+> extends BaseExpression<CustomPathJSON> {
+  static kind: string = ASTKind.KeyPathExpression;
+
+  protected _keyPath: string[] = [];
+
+  /**
+   * The key path of the variable.
+   */
+  get keyPath(): string[] {
+    return this._keyPath;
+  }
+
+  /**
+   * Get the variable fields referenced by the expression.
+   * @returns An array of referenced variable fields.
+   */
+  getRefFields(): BaseVariableField[] {
+    const ref = this.scope.available.getByKeyPath(this._keyPath);
+    return ref ? [ref] : [];
+  }
+
+  /**
+   * The return type of the expression.
+   */
+  get returnType(): BaseType | undefined {
+    const [refNode] = this._refs || [];
+
+    // Get the type of the referenced variable.
+    if (refNode && refNode.flags & ASTNodeFlags.VariableField) {
+      return refNode.type;
+    }
+
+    return;
+  }
+
+  /**
+   * Parse the business-defined path expression into a key path.
+   *
+   * Businesses can quickly customize their own path expressions by modifying this method.
+   * @param json The path expression defined by the business.
+   * @returns The key path.
+   */
+  protected parseToKeyPath(json: CustomPathJSON): string[] {
+    // The default JSON is in KeyPathExpressionJSON format.
+    return (json as unknown as KeyPathExpressionJSON).keyPath;
+  }
+
+  /**
+   * Deserializes the `KeyPathExpressionJSON` to the `KeyPathExpression`.
+   * @param json The `KeyPathExpressionJSON` to deserialize.
+   */
+  fromJSON(json: CustomPathJSON): void {
+    const keyPath = this.parseToKeyPath(json);
+
+    if (!shallowEqual(keyPath, this._keyPath)) {
+      this._keyPath = keyPath;
+
+      // After the keyPath is updated, the referenced variables need to be refreshed.
+      this.refreshRefs();
+    }
+  }
+
+  constructor(params: CreateASTParams, opts: any) {
+    super(params, opts);
+
+    this.toDispose.pushAll([
+      // Can be used when the variable list changes (when there are additions or deletions).
+      this.scope.available.onVariableListChange(() => {
+        this.refreshRefs();
+      }),
+      // When the referable variable pointed to by this._keyPath changes, refresh the reference data.
+      this.scope.available.onAnyVariableChange((_v) => {
+        if (_v.key === this._keyPath[0]) {
+          this.refreshRefs();
+        }
+      }),
+    ]);
+  }
+
+  /**
+   * Serialize the `KeyPathExpression` to `KeyPathExpressionJSON`.
+   * @returns The JSON representation of `KeyPathExpression`.
+   */
+  toJSON(): ASTNodeJSON {
+    return {
+      kind: ASTKind.KeyPathExpression,
+      keyPath: this._keyPath,
+    };
+  }
+}

+ 30 - 3
packages/variable-engine/variable-core/src/ast/expression/wrap-array-expression.ts

@@ -8,12 +8,18 @@ import { ASTKind, ASTNodeJSON } from '../types';
 import { BaseType } from '../type';
 import { BaseExpression } from './base-expression';
 
+/**
+ * ASTNodeJSON representation of `WrapArrayExpression`
+ */
 export interface WrapArrayExpressionJSON {
-  wrapFor: ASTNodeJSON; // 需要被遍历的表达式类型
+  /**
+   * The expression to be wrapped.
+   */
+  wrapFor: ASTNodeJSON;
 }
 
 /**
- * 遍历表达式,对列表进行遍历,获取遍历后的变量类型
+ * Represents a wrap expression, which wraps an expression with an array.
  */
 export class WrapArrayExpression extends BaseExpression<WrapArrayExpressionJSON> {
   static kind: string = ASTKind.WrapArrayExpression;
@@ -22,16 +28,25 @@ export class WrapArrayExpression extends BaseExpression<WrapArrayExpressionJSON>
 
   protected _returnType: BaseType | undefined;
 
+  /**
+   * The expression to be wrapped.
+   */
   get wrapFor() {
     return this._wrapFor;
   }
 
+  /**
+   * The return type of the expression.
+   */
   get returnType(): BaseType | undefined {
     return this._returnType;
   }
 
+  /**
+   * Refresh the return type of the expression.
+   */
   refreshReturnType() {
-    // 被遍历表达式的返回值
+    // The return value of the wrapped expression.
     const childReturnTypeJSON = this.wrapFor?.returnType?.toJSON();
 
     this.updateChildNodeByKey('_returnType', {
@@ -40,14 +55,26 @@ export class WrapArrayExpression extends BaseExpression<WrapArrayExpressionJSON>
     });
   }
 
+  /**
+   * Get the variable fields referenced by the expression.
+   * @returns An empty array, as this expression does not reference any variables.
+   */
   getRefFields(): [] {
     return [];
   }
 
+  /**
+   * Deserializes the `WrapArrayExpressionJSON` to the `WrapArrayExpression`.
+   * @param json The `WrapArrayExpressionJSON` to deserialize.
+   */
   fromJSON({ wrapFor: expression }: WrapArrayExpressionJSON): void {
     this.updateChildNodeByKey('_wrapFor', expression);
   }
 
+  /**
+   * Serialize the `WrapArrayExpression` to `WrapArrayExpressionJSON`.
+   * @returns The JSON representation of `WrapArrayExpression`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.WrapArrayExpression,

+ 75 - 5
packages/variable-engine/variable-core/src/ast/factory.ts

@@ -16,41 +16,83 @@ import {
 import { PropertyJSON, VariableDeclarationJSON, VariableDeclarationListJSON } from './declaration';
 import { ASTNode } from './ast-node';
 
+/**
+ * Variable-core ASTNode factories.
+ */
 export namespace ASTFactory {
   /**
-   * 类型相关
-   * @returns
+   * Type-related factories.
+   */
+
+  /**
+   * Creates a `String` type node.
    */
   export const createString = (json?: StringJSON) => ({
     kind: ASTKind.String,
     ...(json || {}),
   });
+
+  /**
+   * Creates a `Number` type node.
+   */
   export const createNumber = () => ({ kind: ASTKind.Number });
+
+  /**
+   * Creates a `Boolean` type node.
+   */
   export const createBoolean = () => ({ kind: ASTKind.Boolean });
+
+  /**
+   * Creates an `Integer` type node.
+   */
   export const createInteger = () => ({ kind: ASTKind.Integer });
+
+  /**
+   * Creates an `Object` type node.
+   */
   export const createObject = (json: ObjectJSON) => ({
     kind: ASTKind.Object,
     ...json,
   });
+
+  /**
+   * Creates an `Array` type node.
+   */
   export const createArray = (json: ArrayJSON) => ({
     kind: ASTKind.Array,
     ...json,
   });
+
+  /**
+   * Creates a `Map` type node.
+   */
   export const createMap = (json: MapJSON) => ({
     kind: ASTKind.Map,
     ...json,
   });
+
+  /**
+   * Creates a `Union` type node.
+   */
   export const createUnion = (json: UnionJSON) => ({
     kind: ASTKind.Union,
     ...json,
   });
+
+  /**
+   * Creates a `CustomType` node.
+   */
   export const createCustomType = (json: CustomTypeJSON) => ({
     kind: ASTKind.CustomType,
     ...json,
   });
 
   /**
-   * 声明相关
+   * Declaration-related factories.
+   */
+
+  /**
+   * Creates a `VariableDeclaration` node.
    */
   export const createVariableDeclaration = <VariableMeta = any>(
     json: VariableDeclarationJSON<VariableMeta>
@@ -58,33 +100,61 @@ export namespace ASTFactory {
     kind: ASTKind.VariableDeclaration,
     ...json,
   });
+
+  /**
+   * Creates a `Property` node.
+   */
   export const createProperty = <VariableMeta = any>(json: PropertyJSON<VariableMeta>) => ({
     kind: ASTKind.Property,
     ...json,
   });
+
+  /**
+   * Creates a `VariableDeclarationList` node.
+   */
   export const createVariableDeclarationList = (json: VariableDeclarationListJSON) => ({
     kind: ASTKind.VariableDeclarationList,
     ...json,
   });
 
   /**
-   * 表达式相关
+   * Expression-related factories.
+   */
+
+  /**
+   * Creates an `EnumerateExpression` node.
    */
   export const createEnumerateExpression = (json: EnumerateExpressionJSON) => ({
     kind: ASTKind.EnumerateExpression,
     ...json,
   });
+
+  /**
+   * Creates a `KeyPathExpression` node.
+   */
   export const createKeyPathExpression = (json: KeyPathExpressionJSON) => ({
     kind: ASTKind.KeyPathExpression,
     ...json,
   });
+
+  /**
+   * Creates a `WrapArrayExpression` node.
+   */
   export const createWrapArrayExpression = (json: WrapArrayExpressionJSON) => ({
     kind: ASTKind.WrapArrayExpression,
     ...json,
   });
 
   /**
-   * 通过 AST Class 创建
+   * Create by AST Class.
+   */
+
+  /**
+   * Creates Type-Safe ASTNodeJSON object based on the provided AST class.
+   *
+   * @param targetType Target ASTNode class.
+   * @param json The JSON data for the node.
+   * @returns The ASTNode JSON object.
    */
   export const create = <JSON extends ASTNodeJSON>(
     targetType: { kind: string; new (...args: any[]): ASTNode<JSON> },

+ 29 - 7
packages/variable-engine/variable-core/src/ast/flags.ts

@@ -3,26 +3,48 @@
  * SPDX-License-Identifier: MIT
  */
 
+/**
+ * ASTNode flags. Stored in the `flags` property of the `ASTNode`.
+ */
 export enum ASTNodeFlags {
+  /**
+   * None.
+   */
   None = 0,
 
   /**
-   * 变量字段
+   * Variable Field.
    */
   VariableField = 1 << 0,
 
   /**
-   * 表达式
+   * Expression.
    */
   Expression = 1 << 2,
 
   /**
-   * 变量类型
+   * # Variable Type Flags
    */
-  BasicType = 1 << 3, // 基础类型
-  DrilldownType = 1 << 4, // 可下钻的变量类型
-  EnumerateType = 1 << 5, // 可遍历的变量类型
-  UnionType = 1 << 6, // 复合类型,暂时不存在
 
+  /**
+   *  Basic type.
+   */
+  BasicType = 1 << 3,
+  /**
+   * Drillable variable type.
+   */
+  DrilldownType = 1 << 4,
+  /**
+   * Enumerable variable type.
+   */
+  EnumerateType = 1 << 5,
+  /**
+   * Composite type, currently not in use.
+   */
+  UnionType = 1 << 6,
+
+  /**
+   * Variable type.
+   */
   VariableType = BasicType | DrilldownType | EnumerateType | UnionType,
 }

+ 74 - 6
packages/variable-engine/variable-core/src/ast/match.ts

@@ -14,60 +14,128 @@ import {
   type MapType,
   type CustomType,
 } from './type';
-import { type EnumerateExpression, type KeyPathExpression } from './expression';
+import { ASTNodeFlags } from './flags';
 import {
+  type WrapArrayExpression,
+  type EnumerateExpression,
+  type KeyPathExpression,
+} from './expression';
+import {
+  type BaseVariableField,
   type Property,
   type VariableDeclaration,
   type VariableDeclarationList,
 } from './declaration';
 import { type ASTNode } from './ast-node';
 
+/**
+ * Variable-core ASTNode matchers.
+ *
+ * - Typescript code inside if statement will be type guarded.
+ */
 export namespace ASTMatch {
   /**
-   * 类型相关
-   * @returns
+   * # Type-related matchers.
+   */
+
+  /**
+   * Check if the node is a `StringType`.
    */
   export const isString = (node?: ASTNode): node is StringType => node?.kind === ASTKind.String;
 
+  /**
+   * Check if the node is a `NumberType`.
+   */
   export const isNumber = (node?: ASTNode): node is NumberType => node?.kind === ASTKind.Number;
 
+  /**
+   * Check if the node is a `BooleanType`.
+   */
   export const isBoolean = (node?: ASTNode): node is BooleanType => node?.kind === ASTKind.Boolean;
 
+  /**
+   * Check if the node is a `IntegerType`.
+   */
   export const isInteger = (node?: ASTNode): node is IntegerType => node?.kind === ASTKind.Integer;
 
+  /**
+   * Check if the node is a `ObjectType`.
+   */
   export const isObject = (node?: ASTNode): node is ObjectType => node?.kind === ASTKind.Object;
 
+  /**
+   * Check if the node is a `ArrayType`.
+   */
   export const isArray = (node?: ASTNode): node is ArrayType => node?.kind === ASTKind.Array;
 
+  /**
+   * Check if the node is a `MapType`.
+   */
   export const isMap = (node?: ASTNode): node is MapType => node?.kind === ASTKind.Map;
 
+  /**
+   * Check if the node is a `CustomType`.
+   */
   export const isCustomType = (node?: ASTNode): node is CustomType =>
     node?.kind === ASTKind.CustomType;
 
   /**
-   * 声明相关
+   * # Declaration-related matchers.
+   */
+
+  /**
+   * Check if the node is a `VariableDeclaration`.
    */
   export const isVariableDeclaration = <VariableMeta = any>(
     node?: ASTNode
   ): node is VariableDeclaration<VariableMeta> => node?.kind === ASTKind.VariableDeclaration;
 
+  /**
+   * Check if the node is a `Property`.
+   */
   export const isProperty = <VariableMeta = any>(node?: ASTNode): node is Property<VariableMeta> =>
     node?.kind === ASTKind.Property;
 
+  /**
+   * Check if the node is a `BaseVariableField`.
+   */
+  export const isBaseVariableField = (node?: ASTNode): node is BaseVariableField =>
+    !!(node?.flags || 0 & ASTNodeFlags.VariableField);
+
+  /**
+   * Check if the node is a `VariableDeclarationList`.
+   */
   export const isVariableDeclarationList = (node?: ASTNode): node is VariableDeclarationList =>
     node?.kind === ASTKind.VariableDeclarationList;
 
   /**
-   * 表达式相关
+   * # Expression-related matchers.
+   */
+
+  /**
+   * Check if the node is a `EnumerateExpression`.
    */
   export const isEnumerateExpression = (node?: ASTNode): node is EnumerateExpression =>
     node?.kind === ASTKind.EnumerateExpression;
 
+  /**
+   * Check if the node is a `WrapArrayExpression`.
+   */
+  export const isWrapArrayExpression = (node?: ASTNode): node is WrapArrayExpression =>
+    node?.kind === ASTKind.WrapArrayExpression;
+
+  /**
+   * Check if the node is a `KeyPathExpression`.
+   */
   export const isKeyPathExpression = (node?: ASTNode): node is KeyPathExpression =>
     node?.kind === ASTKind.KeyPathExpression;
 
   /**
-   * Check AST Match by ASTClass
+   * Check ASTNode Match by ASTClass
+   *
+   * @param node ASTNode to be checked.
+   * @param targetType Target ASTNode class.
+   * @returns Whether the node is of the target type.
    */
   export function is<TargetASTNode extends ASTNode>(
     node?: ASTNode,

+ 38 - 6
packages/variable-engine/variable-core/src/ast/type/array.ts

@@ -9,37 +9,65 @@ import { ASTNodeFlags } from '../flags';
 import { type BaseVariableField } from '../declaration';
 import { BaseType } from './base-type';
 
+/**
+ * ASTNodeJSON representation of `ArrayType`
+ */
 export interface ArrayJSON {
+  /**
+   * The type of the items in the array.
+   */
   items?: ASTNodeJSONOrKind;
 }
 
+/**
+ * Represents an array type.
+ */
 export class ArrayType extends BaseType<ArrayJSON> {
   public flags: ASTNodeFlags = ASTNodeFlags.DrilldownType | ASTNodeFlags.EnumerateType;
 
   static kind: string = ASTKind.Array;
 
+  /**
+   * The type of the items in the array.
+   */
   items: BaseType;
 
+  /**
+   * Deserializes the `ArrayJSON` to the `ArrayType`.
+   * @param json The `ArrayJSON` to deserialize.
+   */
   fromJSON({ items }: ArrayJSON): void {
     this.updateChildNodeByKey('items', parseTypeJsonOrKind(items));
   }
 
-  // items 类型是否可下钻
+  /**
+   * Whether the items type can be drilled down.
+   */
   get canDrilldownItems(): boolean {
     return !!(this.items?.flags & ASTNodeFlags.DrilldownType);
   }
 
+  /**
+   * Get a variable field by key path.
+   * @param keyPath The key path to search for.
+   * @returns The variable field if found, otherwise `undefined`.
+   */
   getByKeyPath(keyPath: string[]): BaseVariableField | undefined {
     const [curr, ...rest] = keyPath || [];
 
     if (curr === '0' && this.canDrilldownItems) {
-      // 数组第 0 项
+      // The first item of the array.
       return this.items.getByKeyPath(rest);
     }
 
     return undefined;
   }
 
+  /**
+   * Check if the current type is equal to the target type.
+   * @param targetTypeJSONOrKind The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
+   */
   public isTypeEqual(targetTypeJSONOrKind?: ASTNodeJSONOrKind): boolean {
     const targetTypeJSON = parseTypeJsonOrKind(targetTypeJSONOrKind);
     const isSuperEqual = super.isTypeEqual(targetTypeJSONOrKind);
@@ -51,15 +79,15 @@ export class ArrayType extends BaseType<ArrayJSON> {
     return (
       targetTypeJSON &&
       isSuperEqual &&
-      // 弱比较,只需要比较 Kind 即可
+      // Weak comparison, only need to compare the Kind.
       (targetTypeJSON?.weak || this.customStrongEqual(targetTypeJSON))
     );
   }
 
   /**
-   * Array 强比较
-   * @param targetTypeJSON
-   * @returns
+   * Array strong comparison.
+   * @param targetTypeJSON The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
    */
   protected customStrongEqual(targetTypeJSON: ASTNodeJSON): boolean {
     if (!this.items) {
@@ -68,6 +96,10 @@ export class ArrayType extends BaseType<ArrayJSON> {
     return this.items?.isTypeEqual((targetTypeJSON as ArrayJSON).items);
   }
 
+  /**
+   * Serialize the `ArrayType` to `ArrayJSON`
+   * @returns The JSON representation of `ArrayType`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.Array,

+ 16 - 7
packages/variable-engine/variable-core/src/ast/type/base-type.ts

@@ -10,6 +10,11 @@ import { BaseVariableField } from '../declaration';
 import { ASTNode } from '../ast-node';
 import { UnionJSON } from './union';
 
+/**
+ * Base class for all types.
+ *
+ * All other types should extend this class.
+ */
 export abstract class BaseType<JSON extends ASTNodeJSON = any, InjectOpts = any> extends ASTNode<
   JSON,
   InjectOpts
@@ -17,13 +22,14 @@ export abstract class BaseType<JSON extends ASTNodeJSON = any, InjectOpts = any>
   public flags: number = ASTNodeFlags.BasicType;
 
   /**
-   * 类型是否一致
-   * @param targetTypeJSON
+   * Check if the current type is equal to the target type.
+   * @param targetTypeJSONOrKind The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
    */
   public isTypeEqual(targetTypeJSONOrKind?: ASTNodeJSONOrKind): boolean {
     const targetTypeJSON = parseTypeJsonOrKind(targetTypeJSONOrKind);
 
-    // 如果是 Union 类型,有一个子类型保持相等即可
+    // If it is a Union type, it is sufficient for one of the subtypes to be equal.
     if (targetTypeJSON?.kind === ASTKind.Union) {
       return ((targetTypeJSON as UnionJSON)?.types || [])?.some((_subType) =>
         this.isTypeEqual(_subType)
@@ -34,16 +40,19 @@ export abstract class BaseType<JSON extends ASTNodeJSON = any, InjectOpts = any>
   }
 
   /**
-   * 可下钻类型需实现
-   * @param keyPath
+   * Get a variable field by key path.
+   *
+   * This method should be implemented by drillable types.
+   * @param keyPath The key path to search for.
+   * @returns The variable field if found, otherwise `undefined`.
    */
   getByKeyPath(keyPath: string[] = []): BaseVariableField | undefined {
     throw new Error(`Get By Key Path is not implemented for Type: ${this.kind}`);
   }
 
   /**
-   * Get AST JSON for current base type
-   * @returns
+   * Serialize the node to a JSON object.
+   * @returns The JSON representation of the node.
    */
   toJSON(): ASTNodeJSON {
     return {

+ 7 - 0
packages/variable-engine/variable-core/src/ast/type/boolean.ts

@@ -6,9 +6,16 @@
 import { ASTKind } from '../types';
 import { BaseType } from './base-type';
 
+/**
+ * Represents a boolean type.
+ */
 export class BooleanType extends BaseType {
   static kind: string = ASTKind.Boolean;
 
+  /**
+   * Deserializes the `BooleanJSON` to the `BooleanType`.
+   * @param json The `BooleanJSON` to deserialize.
+   */
   fromJSON(): void {
     // noop
   }

+ 22 - 1
packages/variable-engine/variable-core/src/ast/type/custom-type.ts

@@ -8,19 +8,35 @@ import { ASTKind, ASTNodeJSONOrKind } from '../types';
 import { type UnionJSON } from './union';
 import { BaseType } from './base-type';
 
+/**
+ * ASTNodeJSON representation of `CustomType`
+ */
 export interface CustomTypeJSON {
+  /**
+   * The name of the custom type.
+   */
   typeName: string;
 }
 
+/**
+ * Represents a custom type.
+ */
 export class CustomType extends BaseType<CustomTypeJSON> {
   static kind: string = ASTKind.CustomType;
 
   protected _typeName: string;
 
+  /**
+   * The name of the custom type.
+   */
   get typeName(): string {
     return this._typeName;
   }
 
+  /**
+   * Deserializes the `CustomTypeJSON` to the `CustomType`.
+   * @param json The `CustomTypeJSON` to deserialize.
+   */
   fromJSON(json: CustomTypeJSON): void {
     if (this._typeName !== json.typeName) {
       this._typeName = json.typeName;
@@ -28,10 +44,15 @@ export class CustomType extends BaseType<CustomTypeJSON> {
     }
   }
 
+  /**
+   * Check if the current type is equal to the target type.
+   * @param targetTypeJSONOrKind The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
+   */
   public isTypeEqual(targetTypeJSONOrKind?: ASTNodeJSONOrKind): boolean {
     const targetTypeJSON = parseTypeJsonOrKind(targetTypeJSONOrKind);
 
-    // 如果是 Union 类型,有一个子类型保持相等即可
+    // If it is a Union type, it is sufficient for one of the subtypes to be equal.
     if (targetTypeJSON?.kind === ASTKind.Union) {
       return ((targetTypeJSON as UnionJSON)?.types || [])?.some((_subType) =>
         this.isTypeEqual(_subType)

+ 7 - 0
packages/variable-engine/variable-core/src/ast/type/integer.ts

@@ -7,11 +7,18 @@ import { ASTKind } from '../types';
 import { ASTNodeFlags } from '../flags';
 import { BaseType } from './base-type';
 
+/**
+ * Represents an integer type.
+ */
 export class IntegerType extends BaseType {
   public flags: ASTNodeFlags = ASTNodeFlags.BasicType;
 
   static kind: string = ASTKind.Integer;
 
+  /**
+   * Deserializes the `IntegerJSON` to the `IntegerType`.
+   * @param json The `IntegerJSON` to deserialize.
+   */
   fromJSON(): void {
     // noop
   }

+ 36 - 22
packages/variable-engine/variable-core/src/ast/type/map.ts

@@ -7,41 +7,51 @@ import { parseTypeJsonOrKind } from '../utils/helpers';
 import { ASTKind, ASTNodeJSON, ASTNodeJSONOrKind } from '../types';
 import { BaseType } from './base-type';
 
+/**
+ * ASTNodeJSON representation of `MapType`
+ */
 export interface MapJSON {
+  /**
+   * The type of the keys in the map.
+   */
   keyType?: ASTNodeJSONOrKind;
+  /**
+   * The type of the values in the map.
+   */
   valueType?: ASTNodeJSONOrKind;
 }
 
+/**
+ * Represents a map type.
+ */
 export class MapType extends BaseType<MapJSON> {
-  // public flags: ASTNodeFlags = ASTNodeFlags.DrilldownType | ASTNodeFlags.EnumerateType;
-
   static kind: string = ASTKind.Map;
 
+  /**
+   * The type of the keys in the map.
+   */
   keyType: BaseType;
 
+  /**
+   * The type of the values in the map.
+   */
   valueType: BaseType;
 
+  /**
+   * Deserializes the `MapJSON` to the `MapType`.
+   * @param json The `MapJSON` to deserialize.
+   */
   fromJSON({ keyType = ASTKind.String, valueType }: MapJSON): void {
-    // Key 默认为 String
+    // Key defaults to String.
     this.updateChildNodeByKey('keyType', parseTypeJsonOrKind(keyType));
     this.updateChildNodeByKey('valueType', parseTypeJsonOrKind(valueType));
   }
 
-  // Value 类型是否可下钻,后续实现
-  // get canDrilldownValue(): boolean {
-  //   return !!(this.valueType.flags & ASTNodeFlags.DrilldownType);
-  // }
-
-  // getByKeyPath(keyPath: string[]): BaseVariableField | undefined {
-  //   const [curr, ...rest] = keyPath || [];
-
-  //   if (curr === '*' && this.canDrilldownValue) {
-  //     return this.valueType.getByKeyPath(rest);
-  //   }
-
-  //   return undefined;
-  // }
-
+  /**
+   * Check if the current type is equal to the target type.
+   * @param targetTypeJSONOrKind The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
+   */
   public isTypeEqual(targetTypeJSONOrKind?: ASTNodeJSONOrKind): boolean {
     const targetTypeJSON = parseTypeJsonOrKind(targetTypeJSONOrKind);
     const isSuperEqual = super.isTypeEqual(targetTypeJSONOrKind);
@@ -53,15 +63,15 @@ export class MapType extends BaseType<MapJSON> {
     return (
       targetTypeJSON &&
       isSuperEqual &&
-      // 弱比较,只需要比较 Kind 即可
+      // Weak comparison, only need to compare the Kind.
       (targetTypeJSON?.weak || this.customStrongEqual(targetTypeJSON))
     );
   }
 
   /**
-   * Map 强比较
-   * @param targetTypeJSON
-   * @returns
+   * Map strong comparison.
+   * @param targetTypeJSON The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
    */
   protected customStrongEqual(targetTypeJSON: ASTNodeJSON): boolean {
     const { keyType = ASTKind.String, valueType } = targetTypeJSON as MapJSON;
@@ -72,6 +82,10 @@ export class MapType extends BaseType<MapJSON> {
     return isValueTypeEqual && this.keyType?.isTypeEqual(keyType);
   }
 
+  /**
+   * Serialize the node to a JSON object.
+   * @returns The JSON representation of the node.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.Map,

+ 7 - 0
packages/variable-engine/variable-core/src/ast/type/number.ts

@@ -6,9 +6,16 @@
 import { ASTKind } from '../types';
 import { BaseType } from './base-type';
 
+/**
+ * Represents a number type.
+ */
 export class NumberType extends BaseType {
   static kind: string = ASTKind.Number;
 
+  /**
+   * Deserializes the `NumberJSON` to the `NumberType`.
+   * @param json The `NumberJSON` to deserialize.
+   */
   fromJSON(): void {
     // noop
   }

+ 43 - 13
packages/variable-engine/variable-core/src/ast/type/object.ts

@@ -11,13 +11,21 @@ import { ASTNodeFlags } from '../flags';
 import { Property, type PropertyJSON } from '../declaration/property';
 import { BaseType } from './base-type';
 
+/**
+ * ASTNodeJSON representation of `ObjectType`
+ */
 export interface ObjectJSON<VariableMeta = any> {
   /**
-   *  Object 的 properties 一定是 Property 类型,因此业务可以不用填 kind
+   * The properties of the object.
+   *
+   * The `properties` of an Object must be of type `Property`, so the business can omit the `kind` field.
    */
   properties?: PropertyJSON<VariableMeta>[];
 }
 
+/**
+ * Action type for object properties change.
+ */
 export type ObjectPropertiesChangeAction = GlobalEventActionType<
   'ObjectPropertiesChange',
   {
@@ -27,20 +35,33 @@ export type ObjectPropertiesChangeAction = GlobalEventActionType<
   ObjectType
 >;
 
+/**
+ * Represents an object type.
+ */
 export class ObjectType extends BaseType<ObjectJSON> {
   public flags: ASTNodeFlags = ASTNodeFlags.DrilldownType;
 
   static kind: string = ASTKind.Object;
 
+  /**
+   * A map of property keys to `Property` instances.
+   */
   propertyTable: Map<string, Property> = new Map();
 
+  /**
+   * An array of `Property` instances.
+   */
   properties: Property[];
 
+  /**
+   * Deserializes the `ObjectJSON` to the `ObjectType`.
+   * @param json The `ObjectJSON` to deserialize.
+   */
   fromJSON({ properties }: ObjectJSON): void {
     const removedKeys = new Set(this.propertyTable.keys());
     const prev = [...(this.properties || [])];
 
-    // 遍历新的 properties
+    // Iterate over the new properties.
     this.properties = (properties || []).map((property: PropertyJSON) => {
       const existProperty = this.propertyTable.get(property.key);
       removedKeys.delete(property.key);
@@ -58,13 +79,13 @@ export class ObjectType extends BaseType<ObjectJSON> {
         this.fireChange();
 
         this.propertyTable.set(property.key, newProperty);
-        // TODO 子节点主动销毁时,删除表格中的信息
+        // TODO: When a child node is actively destroyed, delete the information in the table.
 
         return newProperty;
       }
     });
 
-    // 删除没有出现过的 property
+    // Delete properties that no longer exist.
     removedKeys.forEach((key) => {
       const property = this.propertyTable.get(key);
       property?.dispose();
@@ -81,6 +102,10 @@ export class ObjectType extends BaseType<ObjectJSON> {
     });
   }
 
+  /**
+   * Serialize the `ObjectType` to `ObjectJSON`.
+   * @returns The JSON representation of `ObjectType`.
+   */
   toJSON(): ASTNodeJSON {
     return {
       kind: ASTKind.Object,
@@ -89,21 +114,21 @@ export class ObjectType extends BaseType<ObjectJSON> {
   }
 
   /**
-   * 根据 KeyPath 找到对应的变量
-   * @param keyPath 变量路径
-   * @returns
+   * Get a variable field by key path.
+   * @param keyPath The key path to search for.
+   * @returns The variable field if found, otherwise `undefined`.
    */
   getByKeyPath(keyPath: string[]): Property | undefined {
     const [curr, ...restKeyPath] = keyPath;
 
     const property = this.propertyTable.get(curr);
 
-    // 找到头了
+    // Found the end of the path.
     if (!restKeyPath.length) {
       return property;
     }
 
-    // 否则继续往下找
+    // Otherwise, continue searching downwards.
     if (property?.type && property?.type?.flags & ASTNodeFlags.DrilldownType) {
       return property.type.getByKeyPath(restKeyPath) as Property | undefined;
     }
@@ -111,6 +136,11 @@ export class ObjectType extends BaseType<ObjectJSON> {
     return undefined;
   }
 
+  /**
+   * Check if the current type is equal to the target type.
+   * @param targetTypeJSONOrKind The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
+   */
   public isTypeEqual(targetTypeJSONOrKind?: ASTNodeJSONOrKind): boolean {
     const targetTypeJSON = parseTypeJsonOrKind(targetTypeJSONOrKind);
     const isSuperEqual = super.isTypeEqual(targetTypeJSONOrKind);
@@ -122,15 +152,15 @@ export class ObjectType extends BaseType<ObjectJSON> {
     return (
       targetTypeJSON &&
       isSuperEqual &&
-      // 弱比较,只需要比较 Kind 即可
+      // Weak comparison, only need to compare the Kind.
       (targetTypeJSON?.weak || this.customStrongEqual(targetTypeJSON))
     );
   }
 
   /**
-   * Object 类型强比较
-   * @param targetTypeJSON
-   * @returns
+   * Object type strong comparison.
+   * @param targetTypeJSON The type to compare with.
+   * @returns `true` if the types are equal, `false` otherwise.
    */
   protected customStrongEqual(targetTypeJSON: ASTNodeJSON): boolean {
     const targetProperties = (targetTypeJSON as ObjectJSON).properties || [];

+ 10 - 2
packages/variable-engine/variable-core/src/ast/type/string.ts

@@ -7,9 +7,12 @@ import { ASTKind } from '../types';
 import { ASTNodeFlags } from '../flags';
 import { BaseType } from './base-type';
 
+/**
+ * ASTNodeJSON representation of the `StringType`.
+ */
 export interface StringJSON {
   /**
-   * https://json-schema.org/understanding-json-schema/reference/type#format
+   * see https://json-schema.org/understanding-json-schema/reference/type#format
    */
   format?: string;
 }
@@ -22,12 +25,17 @@ export class StringType extends BaseType {
   protected _format?: string;
 
   /**
-   * https://json-schema.org/understanding-json-schema/reference/type#format
+   * see https://json-schema.org/understanding-json-schema/reference/string#format
    */
   get format() {
     return this._format;
   }
 
+  /**
+   * Deserialize the `StringJSON` to the `StringType`.
+   *
+   * @param json StringJSON representation of the `StringType`.
+   */
   fromJSON(json?: StringJSON): void {
     if (json?.format !== this._format) {
       this._format = json?.format;

+ 3 - 0
packages/variable-engine/variable-core/src/ast/type/union.ts

@@ -5,6 +5,9 @@
 
 import { ASTNodeJSONOrKind } from '../types';
 
+/**
+ * ASTNodeJSON representation of `UnionType`, which union multiple `BaseType`.
+ */
 export interface UnionJSON {
   types?: ASTNodeJSONOrKind[];
 }

+ 113 - 28
packages/variable-engine/variable-core/src/ast/types.ts

@@ -11,51 +11,126 @@ import { type ASTNode } from './ast-node';
 export type ASTKindType = string;
 export type Identifier = string;
 
+/**
+ * ASTNodeJSON is the JSON representation of an ASTNode.
+ */
 export interface ASTNodeJSON {
+  /**
+   * Kind is the type of the AST node.
+   */
   kind?: ASTKindType;
-  key?: Identifier; // 没有传入时,节点会默认生成一个 key 值
+
+  /**
+   * Key is the unique identifier of the node.
+   * If not provided, the node will generate a default key value.
+   */
+  key?: Identifier;
   [key: string]: any;
 }
 
 /**
- * 核心 AST 节点类型
+ * Core AST node types.
  */
 export enum ASTKind {
   /**
-   * 类型相关
-   * - 内部默认实现一套基于 JSON 类型的类型 AST 节点
+   * # Type-related.
+   * - A set of type AST nodes based on JSON types is implemented internally by default.
+   */
+
+  /**
+   * String type.
+   */
+  String = 'String',
+  /**
+   * Number type.
+   */
+  Number = 'Number',
+  /**
+   * Integer type.
+   */
+  Integer = 'Integer',
+  /**
+   * Boolean type.
+   */
+  Boolean = 'Boolean',
+  /**
+   * Object type.
+   */
+  Object = 'Object',
+  /**
+   * Array type.
+   */
+  Array = 'Array',
+  /**
+   * Map type.
+   */
+  Map = 'Map',
+  /**
+   * Union type.
+   * Commonly used for type checking, generally not exposed to the business.
+   */
+  Union = 'Union',
+  /**
+   * Any type.
+   * Commonly used for business logic.
+   */
+  Any = 'Any',
+  /**
+   * Custom type.
+   * For business-defined types.
+   */
+  CustomType = 'CustomType',
+
+  /**
+   * # Declaration-related.
+   */
+
+  /**
+   * Field definition for Object drill-down.
+   */
+  Property = 'Property',
+  /**
+   * Variable declaration.
+   */
+  VariableDeclaration = 'VariableDeclaration',
+  /**
+   * Variable declaration list.
+   */
+  VariableDeclarationList = 'VariableDeclarationList',
+
+  /**
+   * # Expression-related.
    */
-  String = 'String', // 字符串
-  Number = 'Number', // 数字
-  Integer = 'Integer', // 整数
-  Boolean = 'Boolean', // 布尔值
-  Object = 'Object', // Object
-  Array = 'Array', // Array
-  Map = 'Map', // Map
-  Union = 'Union', // 联合类型,常用于类型判断,一般不对业务透出
-  Any = 'Any', // 任意类型,常用于业务判断
-  CustomType = 'CustomType', // 自定义类型,用于业务自定义类型
 
   /**
-   * 声明
+   * Access fields on variables through the path system.
    */
-  Property = 'Property', // Object 下钻的字段定义
-  VariableDeclaration = 'VariableDeclaration', // 变量声明
-  VariableDeclarationList = 'VariableDeclarationList', // 变量声明
+  KeyPathExpression = 'KeyPathExpression',
+  /**
+   * Iterate over specified data.
+   */
+  EnumerateExpression = 'EnumerateExpression',
+  /**
+   * Wrap with Array Type.
+   */
+  WrapArrayExpression = 'WrapArrayExpression',
 
   /**
-   * 表达式
+   * # General-purpose AST nodes.
    */
-  KeyPathExpression = 'KeyPathExpression', // 通过路径系统访问变量上的字段
-  EnumerateExpression = 'EnumerateExpression', // 对指定的数据进行遍历
-  WrapArrayExpression = 'WrapArrayExpression', // Wrap with Array Type
 
   /**
-   * 通用 AST 节点
+   * General-purpose List<ASTNode> storage node.
+   */
+  ListNode = 'ListNode',
+  /**
+   * General-purpose data storage node.
+   */
+  DataNode = 'DataNode',
+  /**
+   * General-purpose Map<string, ASTNode> storage node.
    */
-  ListNode = 'ListNode', // 通用 List<ASTNode> 存储节点
-  DataNode = 'DataNode', // 通用的数据存储节点
-  MapNode = 'MapNode', // 通用 Map<string, ASTNode> 存储节点
+  MapNode = 'MapNode',
 }
 
 export interface CreateASTParams {
@@ -69,18 +144,24 @@ export type ASTNodeJSONOrKind = string | ASTNodeJSON;
 export type ObserverOrNext<T> = Partial<Observer<T>> | ((value: T) => void);
 
 export interface SubscribeConfig<This, Data> {
-  // 将一个 animationFrame 内的所有变更合并成一个
+  // Merge all changes within one animationFrame into a single one.
   debounceAnimation?: boolean;
-  // 订阅时默认响应一次值
+  // Respond with a value by default upon subscription.
   triggerOnInit?: boolean;
   selector?: (curr: This) => Data;
 }
 
+/**
+ * TypeUtils to get the JSON representation of an AST node with a specific kind.
+ */
 export type GetKindJSON<KindType extends string, JSON extends ASTNodeJSON> = {
   kind: KindType;
   key?: Identifier;
 } & JSON;
 
+/**
+ * TypeUtils to get the JSON representation of an AST node with a specific kind or just the kind string.
+ */
 export type GetKindJSONOrKind<KindType extends string, JSON extends ASTNodeJSON> =
   | ({
       kind: KindType;
@@ -88,6 +169,10 @@ export type GetKindJSONOrKind<KindType extends string, JSON extends ASTNodeJSON>
     } & JSON)
   | KindType;
 
+/**
+ * Global event action type.
+ * - Global event might be dispatched from `ASTNode` or `Scope`.
+ */
 export interface GlobalEventActionType<
   Type = string,
   Payload = any,

+ 12 - 8
packages/variable-engine/variable-core/src/ast/utils/expression.ts

@@ -12,7 +12,11 @@ import { type ASTNode } from '../ast-node';
 import { getParentFields } from './variable-field';
 import { getAllChildren } from './helpers';
 
-// 获取所有子 AST 引用的变量
+/**
+ * Get all variables referenced by child ASTs.
+ * @param ast The ASTNode to traverse.
+ * @returns All variables referenced by child ASTs.
+ */
 export function getAllRefs(ast: ASTNode): BaseVariableField[] {
   return getAllChildren(ast)
     .filter((_child) => _child.flags & ASTNodeFlags.Expression)
@@ -22,16 +26,16 @@ export function getAllRefs(ast: ASTNode): BaseVariableField[] {
 }
 
 /**
- * 检测是否成环
- * @param curr 当前表达式
- * @param refNode 引用的变量节点
- * @returns 是否成环
+ * Checks for circular references.
+ * @param curr The current expression.
+ * @param refNode The referenced variable node.
+ * @returns Whether a circular reference exists.
  */
 export function checkRefCycle(
   curr: BaseExpression,
   refNodes: (BaseVariableField | undefined)[]
 ): boolean {
-  // 作用域没有成环,则不可能成环
+  // If there are no circular references in the scope, then it is impossible to have a circular reference.
   if (
     intersection(curr.scope.coverScopes, refNodes.map((_ref) => _ref?.scope).filter(Boolean))
       .length === 0
@@ -39,7 +43,7 @@ export function checkRefCycle(
     return false;
   }
 
-  // BFS 遍历
+  // BFS traversal.
   const visited = new Set<BaseVariableField>();
   const queue = [...refNodes];
 
@@ -52,6 +56,6 @@ export function checkRefCycle(
     }
   }
 
-  // 引用的变量中,包含表达式的父变量,则成环
+  // If the referenced variables include the parent variable of the expression, then there is a circular reference.
   return intersection(Array.from(visited), getParentFields(curr)).length > 0;
 }

+ 4 - 4
packages/variable-engine/variable-core/src/ast/utils/helpers.ts

@@ -24,11 +24,11 @@ export function updateChildNodeHelper(
   const currNode: ASTNode | undefined = getChildNode();
 
   const isNewKind = currNode?.kind !== nextJSON?.kind;
-  // 如果 nextJSON 没有传入 key 值,则 key 值默认不变
+  // If `nextJSON` does not pass a key value, the key value remains unchanged by default.
   const isNewKey = nextJSON?.key && nextJSON?.key !== currNode?.key;
 
   if (isNewKind || isNewKey) {
-    // 上一个节点需要销毁处理
+    // The previous node needs to be destroyed.
     if (currNode) {
       currNode.dispose();
       removeChildNode();
@@ -40,7 +40,7 @@ export function updateChildNodeHelper(
       this.fireChange();
       return newNode;
     } else {
-      // 直接删除子节点时,也触发更新
+      // Also trigger an update when deleting a child node directly.
       this.fireChange();
     }
   } else if (nextJSON) {
@@ -54,7 +54,7 @@ export function parseTypeJsonOrKind(typeJSONOrKind?: ASTNodeJSONOrKind): ASTNode
   return typeof typeJSONOrKind === 'string' ? { kind: typeJSONOrKind } : typeJSONOrKind;
 }
 
-// 获取所有的 children
+// Get all children.
 export function getAllChildren(ast: ASTNode): ASTNode[] {
   return [...ast.children, ...ast.children.map((_child) => getAllChildren(_child)).flat()];
 }

+ 2 - 2
packages/variable-engine/variable-core/src/ast/utils/inversify.ts

@@ -11,7 +11,7 @@ export const injectToAST = (serviceIdentifier: interfaces.ServiceIdentifier) =>
   function (target: any, propertyKey: string) {
     if (!serviceIdentifier) {
       throw new Error(
-        `ServiceIdentifier ${serviceIdentifier} in @lazyInject is Empty, it might be caused by file circular dependency, please check it.`,
+        `ServiceIdentifier ${serviceIdentifier} in @lazyInject is Empty, it might be caused by file circular dependency, please check it.`
       );
     }
 
@@ -33,7 +33,7 @@ export const injectToAST = (serviceIdentifier: interfaces.ServiceIdentifier) =>
 export const POST_CONSTRUCT_AST_SYMBOL = Symbol('post_construct_ast');
 
 export const postConstructAST = () => (target: any, propertyKey: string) => {
-  // 只运行一次
+  // Only run once.
   if (!Reflect.hasMetadata(POST_CONSTRUCT_AST_SYMBOL, target)) {
     Reflect.defineMetadata(POST_CONSTRUCT_AST_SYMBOL, propertyKey, target);
   } else {

+ 1 - 1
packages/variable-engine/variable-core/src/ast/utils/variable-field.ts

@@ -8,7 +8,7 @@ import { BaseVariableField } from '../declaration';
 import { ASTNode } from '../ast-node';
 
 /**
- * 父变量字段,通过由近而远的方式进行排序
+ * Parent variable fields, sorted from nearest to farthest.
  */
 export function getParentFields(ast: ASTNode): BaseVariableField[] {
   let curr = ast.parent;

+ 8 - 1
packages/variable-engine/variable-core/src/providers.ts

@@ -7,9 +7,16 @@ import { interfaces } from 'inversify';
 
 import { type VariableEngine } from './variable-engine';
 
-// 动态获取 variableEngine,防止出现引用 variableEngine 导致的循环依赖
+/**
+ * A provider for dynamically obtaining the `VariableEngine` instance.
+ * This is used to prevent circular dependencies when injecting `VariableEngine`.
+ */
 export const VariableEngineProvider = Symbol('DynamicVariableEngine');
 export type VariableEngineProvider = () => VariableEngine;
 
+/**
+ * A provider for obtaining the Inversify container instance.
+ * This allows other parts of the application, like AST nodes, to access the container for dependency injection.
+ */
 export const ContainerProvider = Symbol('ContainerProvider');
 export type ContainerProvider = () => interfaces.Container;

+ 52 - 4
packages/variable-engine/variable-core/src/react/context.tsx

@@ -3,7 +3,9 @@
  * SPDX-License-Identifier: MIT
  */
 
-import { createContext, useContext } from 'react';
+/* eslint-disable react/prop-types */
+
+import React, { createContext, useContext } from 'react';
 
 import { Scope } from '../scope';
 
@@ -13,6 +15,52 @@ interface ScopeContextProps {
 
 const ScopeContext = createContext<ScopeContextProps>(null!);
 
-export const ScopeProvider = ScopeContext.Provider;
-export const useScopeContext = (): ScopeContextProps | null => useContext(ScopeContext);
-export const useCurrentScope = () => useContext(ScopeContext)?.scope;
+/**
+ * ScopeProvider provides the scope to its children via context.
+ */
+export const ScopeProvider = (
+  props: React.PropsWithChildren<{
+    /**
+     * scope used in the context
+     */
+    scope?: Scope;
+    /**
+     * @deprecated use scope prop instead, this is kept for backward compatibility
+     */
+    value?: ScopeContextProps;
+  }>
+) => {
+  const { scope, value, children } = props;
+
+  const scopeToUse = scope || value?.scope;
+
+  if (!scopeToUse) {
+    throw new Error('[ScopeProvider] scope is required');
+  }
+
+  return <ScopeContext.Provider value={{ scope: scopeToUse }}>{children}</ScopeContext.Provider>;
+};
+
+/**
+ * useCurrentScope returns the scope provided by ScopeProvider.
+ * @returns
+ */
+export const useCurrentScope = (params?: {
+  /**
+   * whether to throw error when no scope in ScopeProvider is found
+   */
+  strict?: boolean;
+}): Scope => {
+  const { strict = false } = params || {};
+
+  const context = useContext(ScopeContext);
+
+  if (!context) {
+    if (strict) {
+      throw new Error('useCurrentScope must be used within a <ScopeProvider scope={scope}>');
+    }
+    console.warn('useCurrentScope should be used within a <ScopeProvider scope={scope}>');
+  }
+
+  return context?.scope;
+};

+ 4 - 1
packages/variable-engine/variable-core/src/react/hooks/useAvailableVariables.ts

@@ -12,7 +12,10 @@ import { VariableEngine } from '../../variable-engine';
 import { VariableDeclaration } from '../../ast';
 
 /**
- * 获取作用域的可访问变量
+ * Get available variable list in the current scope.
+ *
+ * - If no scope, return global variable list.
+ * - The hook is reactive to variable list or any variables change.
  */
 export function useAvailableVariables(): VariableDeclaration[] {
   const scope = useCurrentScope();

+ 12 - 3
packages/variable-engine/variable-core/src/react/hooks/useScopeAvailable.ts

@@ -11,19 +11,28 @@ import { useCurrentScope } from '../context';
 import { ScopeAvailableData } from '../../scope/datas';
 
 /**
+ * Get the available variables in the current scope.
  * 获取作用域的可访问变量
+ *
+ * @returns the available variables in the current scope
  */
-export function useScopeAvailable(): ScopeAvailableData {
+export function useScopeAvailable(params?: { autoRefresh?: boolean }): ScopeAvailableData {
+  const { autoRefresh = true } = params || {};
+
   const scope = useCurrentScope();
   const refresh = useRefresh();
 
   useEffect(() => {
-    const disposable = scope.available.onDataChange(() => {
+    if (!autoRefresh) {
+      return () => null;
+    }
+
+    const disposable = scope.available.onListOrAnyVarChange(() => {
       refresh();
     });
 
     return () => disposable.dispose();
-  }, []);
+  }, [autoRefresh]);
 
   return scope.available;
 }

+ 1 - 1
packages/variable-engine/variable-core/src/react/index.tsx

@@ -3,6 +3,6 @@
  * SPDX-License-Identifier: MIT
  */
 
-export { ScopeProvider, useCurrentScope, useScopeContext } from './context';
+export { ScopeProvider, useCurrentScope } from './context';
 export { useScopeAvailable } from './hooks/useScopeAvailable';
 export { useAvailableVariables } from './hooks/useAvailableVariables';

+ 42 - 26
packages/variable-engine/variable-core/src/scope/datas/scope-available-data.ts

@@ -28,12 +28,16 @@ import { subsToDisposable } from '../../utils/toDisposable';
 import { createMemo } from '../../utils/memo';
 import { SubscribeConfig } from '../../ast/types';
 import { ASTNode, BaseVariableField, VariableDeclaration } from '../../ast';
+
 /**
- * 作用域可用变量
+ * Manages the available variables within a scope.
  */
 export class ScopeAvailableData {
   protected memo = createMemo();
 
+  /**
+   * The global variable table from the variable engine.
+   */
   get globalVariableTable(): IVariableTable {
     return this.scope.variableEngine.globalVariableTable;
   }
@@ -44,6 +48,9 @@ export class ScopeAvailableData {
 
   protected _variables: VariableDeclaration[] = [];
 
+  /**
+   * The current version of the available data, which increments on each change.
+   */
   get version() {
     return this._version;
   }
@@ -55,9 +62,12 @@ export class ScopeAvailableData {
     }
   }
 
-  // 刷新可访问变量列表
+  /**
+   * Refreshes the list of available variables.
+   * This should be called when the dependencies of the scope change.
+   */
   refresh(): void {
-    // 销毁的作用域不用触发 refresh
+    // Do not trigger refresh for a disposed scope.
     if (this.scope.disposed) {
       return;
     }
@@ -65,23 +75,25 @@ export class ScopeAvailableData {
   }
 
   /**
-   * 监听
+   * An observable that emits when the list of available variables changes.
    */
   protected variables$: Observable<VariableDeclaration[]> = this.refresh$.pipe(
-    // 输出变量是否 version 发生变化
+    // Map to the flattened list of variables from all dependency scopes.
     map(() => flatten(this.depScopes.map((scope) => scope.output.variables || []))),
-    // 变量列表浅比较
+    // Use shallow equality to check if the variable list has changed.
     distinctUntilChanged<VariableDeclaration[]>(shallowEqual),
     share()
   );
 
-  // 监听变量列表中的单个变量变化
+  /**
+   * An observable that emits when any variable in the available list changes its value.
+   */
   protected anyVariableChange$: Observable<VariableDeclaration> = this.variables$.pipe(
     switchMap((_variables) =>
       merge(
         ..._variables.map((_v) =>
           _v.value$.pipe<any>(
-            // 跳过 BehaviorSubject 第一个
+            // Skip the initial value of the BehaviorSubject.
             skip(1)
           )
         )
@@ -91,18 +103,18 @@ export class ScopeAvailableData {
   );
 
   /**
-   * listen to any variable update in list
-   * @param observer
-   * @returns
+   * Subscribes to changes in any variable's value in the available list.
+   * @param observer A function to be called with the changed variable.
+   * @returns A disposable to unsubscribe from the changes.
    */
   onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void) {
     return subsToDisposable(this.anyVariableChange$.subscribe(observer));
   }
 
   /**
-   * listen to variable list change
-   * @param observer
-   * @returns
+   * Subscribes to changes in the list of available variables.
+   * @param observer A function to be called with the new list of variables.
+   * @returns A disposable to unsubscribe from the changes.
    */
   onVariableListChange(observer: (variables: VariableDeclaration[]) => void) {
     return subsToDisposable(this.variables$.subscribe(observer));
@@ -121,7 +133,7 @@ export class ScopeAvailableData {
   public onDataChange = this.onDataChangeEmitter.event;
 
   /**
-   * listen to variable list change + any variable drilldown change
+   * An event that fires when the variable list changes or any variable's value is updated.
    */
   public onListOrAnyVarChange = this.onListOrAnyVarChangeEmitter.event;
 
@@ -147,33 +159,33 @@ export class ScopeAvailableData {
   }
 
   /**
-   * 获取可消费变量
+   * Gets the list of available variables.
    */
   get variables(): VariableDeclaration[] {
     return this._variables;
   }
 
   /**
-   * 获取可访问的变量 keys
+   * Gets the keys of the available variables.
    */
   get variableKeys(): string[] {
     return this.memo('availableKeys', () => this._variables.map((_v) => _v.key));
   }
 
   /**
-   * 返回依赖的作用域
+   * Gets the dependency scopes.
    */
   get depScopes(): Scope[] {
     return this.scope.depScopes;
   }
 
   /**
-   * 通过 keyPath 找到可用变量
-   * @param keyPath
-   * @returns
+   * Retrieves a variable field by its key path from the available variables.
+   * @param keyPath The key path to the variable field.
+   * @returns The found `BaseVariableField` or `undefined`.
    */
   getByKeyPath(keyPath: string[] = []): BaseVariableField | undefined {
-    // 检查变量是否在可访问范围内
+    // Check if the variable is accessible in the current scope.
     if (!this.variableKeys.includes(keyPath[0])) {
       return;
     }
@@ -181,8 +193,12 @@ export class ScopeAvailableData {
   }
 
   /**
-   * Track Variable Change (Includes type update and children update) By KeyPath
-   * @returns
+   * Tracks changes to a variable field by its key path.
+   * This includes changes to its type, value, or any nested properties.
+   * @param keyPath The key path to the variable field to track.
+   * @param cb The callback to execute when the variable changes.
+   * @param opts Configuration options for the subscription.
+   * @returns A disposable to unsubscribe from the tracking.
    */
   trackByKeyPath<Data = BaseVariableField | undefined>(
     keyPath: string[] = [],
@@ -203,13 +219,13 @@ export class ScopeAvailableData {
             (a, b) => shallowEqual(a, b),
             (value) => {
               if (value instanceof ASTNode) {
-                // 如果 value 是 ASTNode,则进行 hash 的比较
+                // If the value is an ASTNode, compare its hash for changes.
                 return value.hash;
               }
               return value;
             }
           ),
-          // 每个 animationFrame 内所有更新合并成一个
+          // Debounce updates to a single emission per animation frame.
           debounceAnimation ? debounceTime(0, animationFrameScheduler) : tap(() => null)
         )
         .subscribe(cb)

+ 18 - 0
packages/variable-engine/variable-core/src/scope/datas/scope-event-data.ts

@@ -14,9 +14,16 @@ type Observer<ActionType extends GlobalEventActionType = GlobalEventActionType>
   action: ActionType
 ) => void;
 
+/**
+ * Manages global events within a scope.
+ */
 export class ScopeEventData {
   event$: Subject<GlobalEventActionType> = new Subject<GlobalEventActionType>();
 
+  /**
+   * Dispatches a global event.
+   * @param action The event action to dispatch.
+   */
   dispatch<ActionType extends GlobalEventActionType = GlobalEventActionType>(action: ActionType) {
     if (this.scope.disposed) {
       return;
@@ -24,12 +31,23 @@ export class ScopeEventData {
     this.event$.next(action);
   }
 
+  /**
+   * Subscribes to all global events.
+   * @param observer The observer function to call with the event action.
+   * @returns A disposable to unsubscribe from the events.
+   */
   subscribe<ActionType extends GlobalEventActionType = GlobalEventActionType>(
     observer: Observer<ActionType>
   ): Disposable {
     return subsToDisposable(this.event$.subscribe(observer as Observer));
   }
 
+  /**
+   * Subscribes to a specific type of global event.
+   * @param type The type of the event to subscribe to.
+   * @param observer The observer function to call with the event action.
+   * @returns A disposable to unsubscribe from the event.
+   */
   on<ActionType extends GlobalEventActionType = GlobalEventActionType>(
     type: ActionType['type'],
     observer: Observer<ActionType>

+ 25 - 11
packages/variable-engine/variable-core/src/scope/datas/scope-output-data.ts

@@ -14,21 +14,30 @@ import { ReSortVariableDeclarationsAction } from '../../ast/declaration/variable
 import { ASTKind, type VariableDeclaration } from '../../ast';
 
 /**
- * 作用域输出
+ * Manages the output variables of a scope.
  */
 export class ScopeOutputData {
   protected variableTable: IVariableTable;
 
   protected memo = createMemo();
 
+  /**
+   * The variable engine instance.
+   */
   get variableEngine(): VariableEngine {
     return this.scope.variableEngine;
   }
 
+  /**
+   * The global variable table from the variable engine.
+   */
   get globalVariableTable(): IVariableTable {
     return this.scope.variableEngine.globalVariableTable;
   }
 
+  /**
+   * The current version of the output data, which increments on each change.
+   */
   get version() {
     return this.variableTable.version;
   }
@@ -41,21 +50,21 @@ export class ScopeOutputData {
   }
 
   /**
-   * listen to variable list change
+   * An event that fires when the list of output variables changes.
    */
   get onVariableListChange() {
     return this.variableTable.onVariableListChange.bind(this.variableTable);
   }
 
   /**
-   * listen to any variable update in list
+   * An event that fires when any output variable's value changes.
    */
   get onAnyVariableChange() {
     return this.variableTable.onAnyVariableChange.bind(this.variableTable);
   }
 
   /**
-   * listen to variable list change + any variable update in list
+   * An event that fires when the output variable list changes or any variable's value is updated.
    */
   get onListOrAnyVarChange() {
     return this.variableTable.onListOrAnyVarChange.bind(this.variableTable);
@@ -64,11 +73,11 @@ export class ScopeOutputData {
   protected _hasChanges = false;
 
   constructor(public readonly scope: Scope) {
-    // setup scope variable table based on globalVariableTable
+    // Setup scope variable table based on globalVariableTable
     this.variableTable = new VariableTable(scope.variableEngine.globalVariableTable);
 
     this.scope.toDispose.pushAll([
-      // When root AST node is updated, check if there are any changes during this AST change
+      // When the root AST node is updated, check if there are any changes.
       this.scope.ast.subscribe(() => {
         if (this._hasChanges) {
           this.memo.clear();
@@ -95,7 +104,7 @@ export class ScopeOutputData {
   }
 
   /**
-   * Scope Output Variable Declarations
+   * The output variable declarations of the scope, sorted by order.
    */
   get variables(): VariableDeclaration[] {
     return this.memo('variables', () =>
@@ -104,13 +113,13 @@ export class ScopeOutputData {
   }
 
   /**
-   * Output Variable Keys
+   * The keys of the output variables.
    */
   get variableKeys(): string[] {
     return this.memo('variableKeys', () => this.variableTable.variableKeys);
   }
 
-  addVariableToTable(variable: VariableDeclaration) {
+  protected addVariableToTable(variable: VariableDeclaration) {
     if (variable.scope !== this.scope) {
       throw Error('VariableDeclaration must be a ast node in scope');
     }
@@ -119,17 +128,22 @@ export class ScopeOutputData {
     this._hasChanges = true;
   }
 
-  removeVariableFromTable(key: string) {
+  protected removeVariableFromTable(key: string) {
     (this.variableTable as VariableTable).removeVariableFromTable(key);
     this._hasChanges = true;
   }
 
+  /**
+   * Retrieves a variable declaration by its key.
+   * @param key The key of the variable.
+   * @returns The `VariableDeclaration` or `undefined` if not found.
+   */
   getVariableByKey(key: string) {
     return this.variableTable.getVariableByKey(key);
   }
 
   /**
-   *
+   * Notifies the covering scopes that the available variables have changed.
    */
   notifyCoversChange(): void {
     this.scope.coverScopes.forEach((scope) => scope.available.refresh());

+ 18 - 7
packages/variable-engine/variable-core/src/scope/scope-chain.ts

@@ -10,8 +10,8 @@ import { VariableEngineProvider } from '../providers';
 import { type Scope } from './scope';
 
 /**
- * 作用域依赖关系管理数据结构
- * - ScopeOrder 可能存在多种实现方式,因此采取抽象类的方式,具体的实现由子类实现
+ * Manages the dependency relationships between scopes.
+ * This is an abstract class, and specific implementations determine how the scope order is managed.
  */
 @injectable()
 export abstract class ScopeChain {
@@ -26,22 +26,33 @@ export abstract class ScopeChain {
   constructor() {}
 
   /**
-   * 所有作用域依赖关系刷新
+   * Refreshes the dependency and coverage relationships for all scopes.
    */
   refreshAllChange(): void {
-    this.variableEngine.getAllScopes().forEach(_scope => {
+    this.variableEngine.getAllScopes().forEach((_scope) => {
       _scope.refreshCovers();
       _scope.refreshDeps();
     });
   }
 
-  // 获取依赖作用域,子类实现
+  /**
+   * Gets the dependency scopes for a given scope.
+   * @param scope The scope to get dependencies for.
+   * @returns An array of dependency scopes.
+   */
   abstract getDeps(scope: Scope): Scope[];
 
-  // 获取覆盖作用域,子类实现
+  /**
+   * Gets the covering scopes for a given scope.
+   * @param scope The scope to get covers for.
+   * @returns An array of covering scopes.
+   */
   abstract getCovers(scope: Scope): Scope[];
 
-  // 获取所有作用域的排序
+  /**
+   * Sorts all scopes based on their dependency relationships.
+   * @returns A sorted array of all scopes.
+   */
   abstract sortAll(): Scope[];
 
   dispose(): void {

+ 46 - 23
packages/variable-engine/variable-core/src/scope/scope.ts

@@ -10,6 +10,9 @@ import { createMemo } from '../utils/memo';
 import { ASTKind, type ASTNode, type ASTNodeJSON, MapNode } from '../ast';
 import { ScopeAvailableData, ScopeEventData, ScopeOutputData } from './datas';
 
+/**
+ * Interface for the Scope constructor.
+ */
 export interface IScopeConstructor {
   new (options: {
     id: string | symbol;
@@ -18,45 +21,50 @@ export interface IScopeConstructor {
   }): Scope;
 }
 
+/**
+ * Represents a variable scope, which manages its own set of variables and their lifecycle.
+ * - `scope.output` represents the variables declared within this scope.
+ * - `scope.available` represents all variables accessible from this scope, including those from parent scopes.
+ */
 export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>> {
   /**
-   * Scope 唯一索引
+   * A unique identifier for the scope.
    */
   readonly id: string | symbol;
 
   /**
-   * Scope 依赖变量引擎
+   * The variable engine instance this scope belongs to.
    */
   readonly variableEngine: VariableEngine;
 
   /**
-   * 作用域的基本元信息,包括作用域所在节点及一些 flag 信息,上层业务可以额外扩展
+   * Metadata associated with the scope, which can be extended by higher-level business logic.
    */
   readonly meta: ScopeMeta;
 
   /**
-   * 作用域 AST 根节点
-   * - Map<formItemKey, formItemValue>
+   * The root AST node for this scope, which is a MapNode.
+   * It stores various data related to the scope, such as `outputs`.
    */
   readonly ast: MapNode;
 
   /**
-   * 可用变量数据管理
+   * Manages the available variables for this scope.
    */
   readonly available: ScopeAvailableData;
 
   /**
-   * 输出变量数据管理
+   * Manages the output variables for this scope.
    */
   readonly output: ScopeOutputData;
 
   /**
-   * 作用域事件管理
+   * Manages event dispatching and handling for this scope.
    */
   readonly event: ScopeEventData;
 
   /**
-   * 数据缓存
+   * A memoization utility for caching computed values.
    */
   protected memo = createMemo();
 
@@ -83,15 +91,24 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
     this.available = new ScopeAvailableData(this);
   }
 
+  /**
+   * Refreshes the covering scopes.
+   */
   refreshCovers(): void {
     this.memo.clear('covers');
   }
 
+  /**
+   * Refreshes the dependency scopes and the available variables.
+   */
   refreshDeps(): void {
     this.memo.clear('deps');
     this.available.refresh();
   }
 
+  /**
+   * Gets the scopes that this scope depends on.
+   */
   get depScopes(): Scope[] {
     return this.memo('deps', () =>
       this.variableEngine.chain
@@ -100,6 +117,9 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
     );
   }
 
+  /**
+   * Gets the scopes that are covered by this scope.
+   */
   get coverScopes(): Scope[] {
     return this.memo('covers', () =>
       this.variableEngine.chain
@@ -108,11 +128,15 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
     );
   }
 
+  /**
+   * Disposes of the scope and its resources.
+   * This will also trigger updates in dependent and covering scopes.
+   */
   dispose(): void {
     this.ast.dispose();
     this.toDispose.dispose();
 
-    // 删除作用域时,触发上下游作用域依赖覆盖更新
+    // When a scope is disposed, update its dependent and covering scopes.
     this.coverScopes.forEach((_scope) => _scope.refreshDeps());
     this.depScopes.forEach((_scope) => _scope.refreshCovers());
   }
@@ -124,19 +148,19 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
   }
 
   /**
-   * Sets a variable in the Scope with the default key 'outputs'.
+   * Sets a variable in the scope with the default key 'outputs'.
    *
-   * @param json - The JSON value to store.
-   * @returns The updated AST node.
+   * @param json The JSON representation of the AST node to set.
+   * @returns The created or updated AST node.
    */
   public setVar<Node extends ASTNode = ASTNode>(json: ASTNodeJSON): Node;
 
   /**
-   * Sets a variable in the Scope by key.
+   * Sets a variable in the scope with a specified key.
    *
-   * @param key - The key of the variable to set.
-   * @param json - The JSON value to store.
-   * @returns The updated AST node.
+   * @param key The key of the variable to set.
+   * @param json The JSON representation of the AST node to set.
+   * @returns The created or updated AST node.
    */
   public setVar<Node extends ASTNode = ASTNode>(key: string, json: ASTNodeJSON): Node;
 
@@ -156,20 +180,19 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
   }
 
   /**
-   * Retrieves a variable from the Scope by key.
+   * Retrieves a variable from the scope by its key.
    *
-   * @param key - The key of the variable to retrieve. Defaults to 'outputs'.
-   * @returns The value of the variable, or undefined if not found.
+   * @param key The key of the variable to retrieve. Defaults to 'outputs'.
+   * @returns The AST node for the variable, or `undefined` if not found.
    */
   public getVar<Node extends ASTNode = ASTNode>(key: string = 'outputs') {
     return this.ast.get<Node>(key);
   }
 
   /**
-   * Clears a variable from the Scope by key.
+   * Clears a variable from the scope by its key.
    *
-   * @param key - The key of the variable to clear. Defaults to 'outputs'.
-   * @returns The updated AST node.
+   * @param key The key of the variable to clear. Defaults to 'outputs'.
    */
   public clearVar(key: string = 'outputs') {
     return this.ast.remove(key);

+ 72 - 6
packages/variable-engine/variable-core/src/scope/types.ts

@@ -7,30 +7,96 @@ import { Event, Disposable } from '@flowgram.ai/utils';
 
 import { BaseVariableField, VariableDeclaration } from '../ast';
 import { type Scope } from './scope';
-// 获取所有作用域的参数
+
+/**
+ * Parameters for getting all scopes.
+ */
 export interface GetAllScopeParams {
-  // 是否排序
+  /**
+   * Whether to sort the scopes.
+   */
   sort?: boolean;
 }
+
+/**
+ * Action type for scope changes.
+ */
 export interface ScopeChangeAction {
   type: 'add' | 'delete' | 'update' | 'available';
   scope: Scope;
 }
 
+/**
+ * Interface for a variable table.
+ */
 export interface IVariableTable extends Disposable {
-  parentTable?: IVariableTable; // 父变量表,会包含所有子表的变量
+  /**
+   * The parent variable table.
+   */
+  parentTable?: IVariableTable;
+
+  /**
+   * @deprecated Use `onVariableListChange` or `onAnyVariableChange` instead.
+   */
   onDataChange: Event<void>;
+
+  /**
+   * The current version of the variable table.
+   */
   version: number;
+
+  /**
+   * The list of variables in the table.
+   */
   variables: VariableDeclaration[];
+
+  /**
+   * The keys of the variables in the table.
+   */
   variableKeys: string[];
+
+  /**
+   * Fires a change event.
+   */
   fireChange(): void;
+
+  /**
+   * Gets a variable or property by its key path.
+   * @param keyPath The key path to the variable or property.
+   * @returns The found `BaseVariableField` or `undefined`.
+   */
   getByKeyPath(keyPath: string[]): BaseVariableField | undefined;
+
+  /**
+   * Gets a variable by its key.
+   * @param key The key of the variable.
+   * @returns The found `VariableDeclaration` or `undefined`.
+   */
   getVariableByKey(key: string): VariableDeclaration | undefined;
-  // 方法不对外透出,仅内部使用
-  // addVariableToTable(variable: VariableDeclaration): void;
-  // removeVariableFromTable(key: string): void;
+
+  /**
+   * Disposes the variable table.
+   */
   dispose(): void;
+
+  /**
+   * Subscribes to changes in the variable list.
+   * @param observer The observer function.
+   * @returns A disposable to unsubscribe.
+   */
   onVariableListChange(observer: (variables: VariableDeclaration[]) => void): Disposable;
+
+  /**
+   * Subscribes to changes in any variable's value.
+   * @param observer The observer function.
+   * @returns A disposable to unsubscribe.
+   */
   onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void): Disposable;
+
+  /**
+   * Subscribes to both variable list changes and any variable's value changes.
+   * @param observer The observer function.
+   * @returns A disposable to unsubscribe.
+   */
   onListOrAnyVarChange(observer: () => void): Disposable;
 }

+ 55 - 23
packages/variable-engine/variable-core/src/scope/variable-table.ts

@@ -11,6 +11,11 @@ import { BaseVariableField } from '../ast/declaration/base-variable-field';
 import { VariableDeclaration } from '../ast';
 import { IVariableTable } from './types';
 
+/**
+ * A class that stores and manages variables in a table-like structure.
+ * It provides methods for adding, removing, and retrieving variables, as well as
+ * observables for listening to changes in the variable list and individual variables.
+ */
 export class VariableTable implements IVariableTable {
   protected table: Map<string, VariableDeclaration> = new Map();
 
@@ -23,13 +28,15 @@ export class VariableTable implements IVariableTable {
 
   protected variables$: Subject<VariableDeclaration[]> = new Subject<VariableDeclaration[]>();
 
-  // 监听变量列表中的单个变量变化
+  /**
+   * An observable that listens for value changes on any variable within the table.
+   */
   protected anyVariableChange$: Observable<VariableDeclaration> = this.variables$.pipe(
     switchMap((_variables) =>
       merge(
         ..._variables.map((_v) =>
           _v.value$.pipe<any>(
-            // 跳过 BehaviorSubject 第一个
+            // Skip the initial value of the BehaviorSubject
             skip(1)
           )
         )
@@ -39,26 +46,27 @@ export class VariableTable implements IVariableTable {
   );
 
   /**
-   * listen to any variable update in list
-   * @param observer
-   * @returns
+   * Subscribes to updates on any variable in the list.
+   * @param observer A function to be called when any variable's value changes.
+   * @returns A disposable object to unsubscribe from the updates.
    */
   onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void) {
     return subsToDisposable(this.anyVariableChange$.subscribe(observer));
   }
 
   /**
-   * listen to variable list change
-   * @param observer
-   * @returns
+   * Subscribes to changes in the variable list (additions or removals).
+   * @param observer A function to be called when the list of variables changes.
+   * @returns A disposable object to unsubscribe from the updates.
    */
   onVariableListChange(observer: (variables: VariableDeclaration[]) => void) {
     return subsToDisposable(this.variables$.subscribe(observer));
   }
 
   /**
-   * listen to variable list change + any variable update in list
-   * @param observer
+   * Subscribes to both variable list changes and updates to any variable in the list.
+   * @param observer A function to be called when either the list or a variable in it changes.
+   * @returns A disposable collection to unsubscribe from both events.
    */
   onListOrAnyVarChange(observer: () => void) {
     const disposables = new DisposableCollection();
@@ -67,12 +75,15 @@ export class VariableTable implements IVariableTable {
   }
 
   /**
-   * @deprecated use onListOrAnyVarChange instead
+   * @deprecated Use onListOrAnyVarChange instead.
    */
   public onDataChange = this.onDataChangeEmitter.event;
 
   protected _version: number = 0;
 
+  /**
+   * Fires change events to notify listeners that the data has been updated.
+   */
   fireChange() {
     this.bumpVersion();
     this.onDataChangeEmitter.fire();
@@ -80,10 +91,16 @@ export class VariableTable implements IVariableTable {
     this.parentTable?.fireChange();
   }
 
+  /**
+   * The current version of the variable table, incremented on each change.
+   */
   get version(): number {
     return this._version;
   }
 
+  /**
+   * Increments the version number, resetting to 0 if it reaches MAX_SAFE_INTEGER.
+   */
   protected bumpVersion() {
     this._version = this._version + 1;
     if (this._version === Number.MAX_SAFE_INTEGER) {
@@ -92,29 +109,39 @@ export class VariableTable implements IVariableTable {
   }
 
   constructor(
-    public parentTable?: IVariableTable // 父变量表,会包含所有子表的变量
+    /**
+     * An optional parent table. If provided, this table will contain all variables
+     * from the current table.
+     */
+    public parentTable?: IVariableTable
   ) {
     this.toDispose.pushAll([
       this.onDataChangeEmitter,
-      // active share()
+      // Activate the share() operator
       this.onAnyVariableChange(() => {
         this.bumpVersion();
       }),
     ]);
   }
 
+  /**
+   * An array of all variables in the table.
+   */
   get variables(): VariableDeclaration[] {
     return Array.from(this.table.values());
   }
 
+  /**
+   * An array of all variable keys in the table.
+   */
   get variableKeys(): string[] {
     return Array.from(this.table.keys());
   }
 
   /**
-   * 根据 keyPath 找到对应的变量,或 Property 节点
-   * @param keyPath
-   * @returns
+   * Retrieves a variable or a nested property field by its key path.
+   * @param keyPath An array of keys representing the path to the desired field.
+   * @returns The found variable or property field, or undefined if not found.
    */
   getByKeyPath(keyPath: string[]): BaseVariableField | undefined {
     const [variableKey, ...propertyKeys] = keyPath || [];
@@ -129,17 +156,18 @@ export class VariableTable implements IVariableTable {
   }
 
   /**
-   * 根据 key 值找到相应的变量
-   * @param key
-   * @returns
+   * Retrieves a variable by its key.
+   * @param key The key of the variable to retrieve.
+   * @returns The variable declaration if found, otherwise undefined.
    */
   getVariableByKey(key: string) {
     return this.table.get(key);
   }
 
   /**
-   * 往 variableTable 添加输出变量
-   * @param variable
+   * Adds a variable to the table.
+   * If a parent table exists, the variable is also added to the parent.
+   * @param variable The variable declaration to add.
    */
   addVariableToTable(variable: VariableDeclaration) {
     this.table.set(variable.key, variable);
@@ -149,8 +177,9 @@ export class VariableTable implements IVariableTable {
   }
 
   /**
-   * 从 variableTable 中移除变量
-   * @param key
+   * Removes a variable from the table.
+   * If a parent table exists, the variable is also removed from the parent.
+   * @param key The key of the variable to remove.
    */
   removeVariableFromTable(key: string) {
     this.table.delete(key);
@@ -159,6 +188,9 @@ export class VariableTable implements IVariableTable {
     }
   }
 
+  /**
+   * Disposes of all resources used by the variable table.
+   */
   dispose(): void {
     this.variableKeys.forEach((_key) =>
       (this.parentTable as VariableTable)?.removeVariableFromTable(_key)

+ 29 - 4
packages/variable-engine/variable-core/src/services/variable-field-key-rename-service.ts

@@ -20,6 +20,11 @@ interface RenameInfo {
   after: BaseVariableField;
 }
 
+/**
+ * This service is responsible for detecting when a variable field's key is renamed.
+ * It listens for changes in variable declaration lists and object properties, and
+ * determines if a change constitutes a rename operation.
+ */
 @injectable()
 export class VariableFieldKeyRenameService {
   @inject(VariableEngine) variableEngine: VariableEngine;
@@ -28,21 +33,36 @@ export class VariableFieldKeyRenameService {
 
   renameEmitter = new Emitter<RenameInfo>();
 
-  // 没有被 rename 的字段通过 disposeInList 透出,让业务区分变量是 rename 删除的,还是真正从列表中删除的
+  /**
+   * Emits events for fields that are disposed of during a list change, but not renamed.
+   * This helps distinguish between a field that was truly removed and one that was renamed.
+   */
   disposeInListEmitter = new Emitter<BaseVariableField>();
 
+  /**
+   * An event that fires when a variable field key is successfully renamed.
+   */
   onRename = this.renameEmitter.event;
 
+  /**
+   * An event that fires when a field is removed from a list (and not part of a rename).
+   */
   onDisposeInList = this.disposeInListEmitter.event;
 
+  /**
+   * Handles changes in a list of fields to detect rename operations.
+   * @param ast The AST node where the change occurred.
+   * @param prev The list of fields before the change.
+   * @param next The list of fields after the change.
+   */
   handleFieldListChange(ast?: ASTNode, prev?: BaseVariableField[], next?: BaseVariableField[]) {
-    // 1. 检查是否触发 ReName
+    // 1. Check if a rename is possible.
     if (!ast || !prev?.length || !next?.length) {
       this.notifyFieldsDispose(prev, next);
       return;
     }
 
-    // 2. 改动前后长度需要一致
+    // 2. The lengths of the lists must be the same for a rename.
     if (prev.length !== next.length) {
       this.notifyFieldsDispose(prev, next);
       return;
@@ -55,7 +75,7 @@ export class VariableFieldKeyRenameService {
       const nextField = next[index];
 
       if (prevField.key !== nextField.key) {
-        // 一次只能存在一行信息 ReName
+        // Only one rename is allowed at a time.
         if (existFieldChanged) {
           this.notifyFieldsDispose(prev, next);
           return;
@@ -76,6 +96,11 @@ export class VariableFieldKeyRenameService {
     this.renameEmitter.fire(renameNodeInfo);
   }
 
+  /**
+   * Notifies listeners about fields that were removed from a list.
+   * @param prev The list of fields before the change.
+   * @param next The list of fields after the change.
+   */
   notifyFieldsDispose(prev?: BaseVariableField[], next?: BaseVariableField[]) {
     const removedFields = difference(prev || [], next || []);
     removedFields.forEach((_field) => this.disposeInListEmitter.fire(_field));

+ 1 - 1
packages/variable-engine/variable-core/src/utils/memo.ts

@@ -6,7 +6,7 @@
 type KeyType = string | symbol;
 
 /**
- * 创建缓存管理器
+ * Create memo manager
  * @returns
  */
 export const createMemo = (): {

+ 5 - 0
packages/variable-engine/variable-core/src/utils/toDisposable.tsx

@@ -6,6 +6,11 @@
 import { Subscription } from 'rxjs';
 import { Disposable } from '@flowgram.ai/utils';
 
+/**
+ * Convert rxjs subscription to disposable
+ * @param subscription - The rxjs subscription
+ * @returns The disposable
+ */
 export function subsToDisposable(subscription: Subscription): Disposable {
   return Disposable.create(() => subscription.unsubscribe());
 }

+ 9 - 5
packages/variable-engine/variable-core/src/variable-container-module.ts

@@ -10,15 +10,19 @@ import { VariableFieldKeyRenameService } from './services';
 import { ContainerProvider, VariableEngineProvider } from './providers';
 import { ASTRegisters } from './ast';
 
-export const VariableContainerModule = new ContainerModule(bind => {
+/**
+ * An InversifyJS container module that binds all the necessary services for the variable engine.
+ * This module sets up the dependency injection for the core components of the variable engine.
+ */
+export const VariableContainerModule = new ContainerModule((bind) => {
   bind(VariableEngine).toSelf().inSingletonScope();
   bind(ASTRegisters).toSelf().inSingletonScope();
 
   bind(VariableFieldKeyRenameService).toSelf().inSingletonScope();
 
-  // 提供 provider 注入 variableEngine,防止部分场景下的循环依赖
-  bind(VariableEngineProvider).toDynamicValue(ctx => () => ctx.container.get(VariableEngine));
+  // Provide a dynamic provider for VariableEngine to prevent circular dependencies.
+  bind(VariableEngineProvider).toDynamicValue((ctx) => () => ctx.container.get(VariableEngine));
 
-  // 提供 Container Provider 方便 AST 注入模块
-  bind(ContainerProvider).toDynamicValue(ctx => () => ctx.container);
+  // Provide a ContainerProvider to allow AST nodes and other components to access the container.
+  bind(ContainerProvider).toDynamicValue((ctx) => () => ctx.container);
 });

+ 62 - 16
packages/variable-engine/variable-core/src/variable-engine.ts

@@ -17,6 +17,10 @@ import { Scope, ScopeChain, type IVariableTable } from './scope';
 import { ContainerProvider } from './providers';
 import { ASTRegisters, type GlobalEventActionType } from './ast';
 
+/**
+ * The core of the variable engine system.
+ * It manages scopes, variables, and events within the system.
+ */
 @injectable()
 export class VariableEngine implements Disposable {
   protected toDispose = new DisposableCollection();
@@ -25,56 +29,86 @@ export class VariableEngine implements Disposable {
 
   protected scopeMap = new Map<string | symbol, Scope>();
 
+  /**
+   * A rxjs subject that emits global events occurring within the variable engine.
+   */
   globalEvent$: Subject<GlobalEventActionType> = new Subject<GlobalEventActionType>();
 
   protected onScopeChangeEmitter = new Emitter<ScopeChangeAction>();
 
+  /**
+   * A table containing all global variables.
+   */
   public globalVariableTable: IVariableTable = new VariableTable();
 
+  /**
+   * An event that fires whenever a scope is added, updated, or deleted.
+   */
   public onScopeChange = this.onScopeChangeEmitter.event;
 
-  // 获取 inversify container 容器
   @inject(ContainerProvider) private readonly containerProvider: ContainerProvider;
 
+  /**
+   * The Inversify container instance.
+   */
   get container(): interfaces.Container {
     return this.containerProvider();
   }
 
   constructor(
-    @inject(ScopeChain) public readonly chain: ScopeChain, // 作用域依赖关系偏序集
-    @inject(ASTRegisters) public readonly astRegisters: ASTRegisters // AST 节点注册管理器
+    /**
+     * The scope chain, which manages the dependency relationships between scopes.
+     */
+    @inject(ScopeChain)
+    public readonly chain: ScopeChain,
+    /**
+     * The registry for all AST node types.
+     */
+    @inject(ASTRegisters)
+    public readonly astRegisters: ASTRegisters
   ) {
     this.toDispose.pushAll([
       chain,
       Disposable.create(() => {
-        // 清空所有作用域
+        // Dispose all scopes
         this.getAllScopes().forEach((scope) => scope.dispose());
         this.globalVariableTable.dispose();
       }),
     ]);
   }
 
+  /**
+   * Disposes of all resources used by the variable engine.
+   */
   @preDestroy()
   dispose(): void {
     this.toDispose.dispose();
   }
 
-  // 根据 scopeId 找到作用域
+  /**
+   * Retrieves a scope by its unique identifier.
+   * @param scopeId The ID of the scope to retrieve.
+   * @returns The scope if found, otherwise undefined.
+   */
   getScopeById(scopeId: string | symbol): Scope | undefined {
     return this.scopeMap.get(scopeId);
   }
 
-  // 移除作用域
+  /**
+   * Removes a scope by its unique identifier and disposes of it.
+   * @param scopeId The ID of the scope to remove.
+   */
   removeScopeById(scopeId: string | symbol): void {
     this.getScopeById(scopeId)?.dispose();
   }
 
   /**
-   * Get Scope, if Scope exists and type is same, will use it directly
-   * @param id scope id
-   * @param meta scope meta, defined by user
-   * @param ScopeConstructor scope constructor, default is Scope. you can extends Scope to create your own scope
-   * @returns
+   * Creates a new scope or retrieves an existing one if the ID and type match.
+   * @param id The unique identifier for the scope.
+   * @param meta Optional metadata for the scope, defined by the user.
+   * @param options Options for creating the scope.
+   * @param options.ScopeConstructor The constructor to use for creating the scope. Defaults to `Scope`.
+   * @returns The created or existing scope.
    */
   createScope(
     id: string | symbol,
@@ -96,7 +130,7 @@ export class VariableEngine implements Disposable {
         scope.ast.subscribe(() => {
           this.onScopeChangeEmitter.fire({ type: 'update', scope: scope! });
         }),
-        // 可用变量发生变化
+        // Fires when available variables change
         scope.available.onDataChange(() => {
           this.onScopeChangeEmitter.fire({ type: 'available', scope: scope! });
         }),
@@ -110,16 +144,19 @@ export class VariableEngine implements Disposable {
     return scope;
   }
 
-  // 获取系统中所有的作用域
+  /**
+   * Retrieves all scopes currently managed by the engine.
+   * @param options Options for retrieving the scopes.
+   * @param options.sort Whether to sort the scopes based on their dependency chain.
+   * @returns An array of all scopes.
+   */
   getAllScopes({
     sort,
   }: {
-    // 是否排序
     sort?: boolean;
   } = {}): Scope[] {
     const allScopes = Array.from(this.scopeMap.values());
 
-    // 是否进行排序
     if (sort) {
       const sortScopes = this.chain.sortAll();
       const remainScopes = new Set(allScopes);
@@ -128,14 +165,23 @@ export class VariableEngine implements Disposable {
       return [...sortScopes, ...Array.from(remainScopes)];
     }
 
-    // 数据拷贝一份
     return [...allScopes];
   }
 
+  /**
+   * Fires a global event to be broadcast to all listeners.
+   * @param event The global event to fire.
+   */
   fireGlobalEvent(event: GlobalEventActionType) {
     this.globalEvent$.next(event);
   }
 
+  /**
+   * Subscribes to a specific type of global event.
+   * @param type The type of the event to listen for.
+   * @param observer A function to be called when the event is observed.
+   * @returns A disposable object to unsubscribe from the event.
+   */
   onGlobalEvent<ActionType extends GlobalEventActionType = GlobalEventActionType>(
     type: ActionType['type'],
     observer: (action: ActionType) => void

+ 62 - 30
packages/variable-engine/variable-layout/src/chains/fixed-layout-scope-chain.ts

@@ -15,10 +15,10 @@ import { GlobalScope } from '../scopes/global-scope';
 import { FlowNodeVariableData } from '../flow-node-variable-data';
 
 /**
- * 基于 FlowVirtualTree 的 ScopeOrder 实现
+ * Scope chain implementation based on `FlowVirtualTree`.
  */
 export class FixedLayoutScopeChain extends ScopeChain {
-  // 增加  { id: string } 使得可以灵活添加自定义虚拟节点
+  // By adding { id: string }, custom virtual nodes can be flexibly added
   tree: FlowVirtualTree<ScopeChainNode> | undefined;
 
   @inject(ScopeChainTransformService)
@@ -33,24 +33,31 @@ export class FixedLayoutScopeChain extends ScopeChain {
   ) {
     super();
 
-    // 绑定 flowDocument 里面的树
+    // Bind the tree in flowDocument
     this.bindTree(flowDocument.originTree);
 
-    // originTree 发生变化时,触发依赖关系的变化
+    // When originTree changes, trigger changes in dependencies
     this.toDispose.push(
-      // REFRACTOR: onTreeChange 触发时机精细化
+      // REFRACTOR: onTreeChange trigger timing needs to be refined
       flowDocument.originTree.onTreeChange(() => {
         this.refreshAllChange();
       })
     );
   }
 
-  // 绑定树
+  /**
+   * Binds the scope chain to a `FlowVirtualTree`.
+   * @param tree The `FlowVirtualTree` to bind to.
+   */
   bindTree(tree: FlowVirtualTree<ScopeChainNode>): void {
     this.tree = tree;
   }
 
-  // 获取依赖作用域
+  /**
+   * Gets the dependency scopes for a given scope.
+   * @param scope The scope to get dependencies for.
+   * @returns An array of dependency scopes.
+   */
   getDeps(scope: FlowNodeScope): FlowNodeScope[] {
     if (!this.tree) {
       return this.transformService.transformDeps([], { scope });
@@ -69,15 +76,15 @@ export class FixedLayoutScopeChain extends ScopeChain {
       const { parent, pre } = this.tree.getInfo(curr);
       const currData = this.getVariableData(curr);
 
-      // 包含子节点,且不是私有作用域
+      // Contains child nodes and is not a private scope
 
       if (curr === node) {
-        // public 可以依赖 private
+        // public can depend on private
         if (scope.meta.type === FlowNodeScopeTypeEnum.public && currData?.private) {
           deps.unshift(currData.private);
         }
       } else if (this.hasChildren(curr) && !this.isNodeChildrenPrivate(curr)) {
-        // 有子元素的节点,则将子元素纳入依赖作用域
+        // For nodes with child elements, include the child elements in the dependency scope
         deps.unshift(
           ...this.getAllSortedChildScope(curr, {
             ignoreNodeChildrenPrivate: true,
@@ -85,30 +92,30 @@ export class FixedLayoutScopeChain extends ScopeChain {
         );
       }
 
-      // 节点的 public 都可以被访问
+      // The public of the node can be accessed
       if (currData && curr !== node) {
         deps.unshift(currData.public);
       }
 
-      // 上个节点处理
+      // Process the previous node
       if (pre) {
         curr = pre;
         continue;
       }
 
-      // 父节点处理
+      // Process the parent node
       if (parent) {
         let currParent: ScopeChainNode | undefined = parent;
         let currParentPre: ScopeChainNode | undefined = this.tree.getPre(currParent);
 
         while (currParent) {
-          // 父节点的 private 和 public 都能被子节点访问
+          // Both private and public of the parent node can be accessed by child nodes
           const currParentData = this.getVariableData(currParent);
           if (currParentData) {
             deps.unshift(...currParentData.allScopes);
           }
 
-          // 当前 parent 有 pre 节点,则停止向上查找
+          // If the current parent has a pre node, stop searching upwards
           if (currParentPre) {
             break;
           }
@@ -120,7 +127,7 @@ export class FixedLayoutScopeChain extends ScopeChain {
         continue;
       }
 
-      // next 和 parent 都没有,直接结束循环
+      // If there is no next and no parent, end the loop directly
       curr = undefined;
     }
 
@@ -133,7 +140,11 @@ export class FixedLayoutScopeChain extends ScopeChain {
     return this.transformService.transformDeps(deps, { scope });
   }
 
-  // 获取覆盖作用域
+  /**
+   * Gets the covering scopes for a given scope.
+   * @param scope The scope to get covering scopes for.
+   * @returns An array of covering scopes.
+   */
   getCovers(scope: FlowNodeScope): FlowNodeScope[] {
     if (!this.tree) {
       return this.transformService.transformCovers([], { scope });
@@ -155,7 +166,7 @@ export class FixedLayoutScopeChain extends ScopeChain {
 
     const covers: FlowNodeScope[] = [];
 
-    // 如果是 private 作用域,则只能子节点访问
+    // If it is a private scope, only child nodes can access it
     if (scope.meta.type === FlowNodeScopeTypeEnum.private) {
       covers.push(
         ...this.getAllSortedChildScope(node, {
@@ -171,7 +182,7 @@ export class FixedLayoutScopeChain extends ScopeChain {
       const { next, parent } = this.tree.getInfo(curr);
       const currData = this.getVariableData(curr);
 
-      // 有子元素的节点,则将子元素纳入覆盖作用域
+      // For nodes with child elements, include the child elements in the covering scope
       if (curr !== node) {
         if (this.hasChildren(curr)) {
           covers.push(
@@ -184,7 +195,7 @@ export class FixedLayoutScopeChain extends ScopeChain {
         }
       }
 
-      // 下个节点处理
+      // Process the next node
       if (next) {
         curr = next;
         continue;
@@ -195,12 +206,12 @@ export class FixedLayoutScopeChain extends ScopeChain {
         let currParentNext: ScopeChainNode | undefined = this.tree.getNext(currParent);
 
         while (currParent) {
-          // 私有作用域不能被后续节点访问
+          // Private scopes cannot be accessed by subsequent nodes
           if (this.isNodeChildrenPrivate(currParent)) {
             return this.transformService.transformCovers(covers, { scope });
           }
 
-          // 当前 parent 有 next 节点,则停止向上查找
+          // If the current parent has a next node, stop searching upwards
           if (currParentNext) {
             break;
           }
@@ -223,7 +234,10 @@ export class FixedLayoutScopeChain extends ScopeChain {
     return this.transformService.transformCovers(covers, { scope });
   }
 
-  // 排序所有作用域
+  /**
+   * Sorts all scopes in the scope chain.
+   * @returns A sorted array of all scopes.
+   */
   sortAll(): Scope[] {
     const startNode = this.flowDocument.getAllNodes().find((_node) => _node.isStart);
     if (!startNode) {
@@ -241,12 +255,16 @@ export class FixedLayoutScopeChain extends ScopeChain {
     return [...deps, startPublicScope, ...covers];
   }
 
-  // 获取变量 Data 数据
+  /**
+   * Gets the `FlowNodeVariableData` for a given `ScopeChainNode`.
+   * @param node The `ScopeChainNode` to get data for.
+   * @returns The `FlowNodeVariableData` or `undefined` if not found.
+   */
   private getVariableData(node: ScopeChainNode): FlowNodeVariableData | undefined {
     if (node.flowNodeType === 'virtualNode') {
       return;
     }
-    // TODO 包含 $ 的节点不注册 variableData
+    // TODO Nodes containing $ do not register variableData
     if (node.id.startsWith('$')) {
       return;
     }
@@ -254,22 +272,36 @@ export class FixedLayoutScopeChain extends ScopeChain {
     return (node as FlowNodeEntity).getData(FlowNodeVariableData);
   }
 
-  // 子节点不可以被后续节点访问
+  /**
+   * Checks if the children of a node are private.
+   * @param node The node to check.
+   * @returns `true` if the children are private, `false` otherwise.
+   */
   private isNodeChildrenPrivate(node?: ScopeChainNode): boolean {
     if (this.configs?.isNodeChildrenPrivate) {
       return node ? this.configs?.isNodeChildrenPrivate(node) : false;
     }
 
     const isSystemNode = node?.id.startsWith('$');
-    // 兜底:有子节点(节点 id 没有 $ 开头)的全部为私有作用域
+    // Fallback: all nodes with children (node id does not start with $) are private scopes
     return !isSystemNode && this.hasChildren(node);
   }
 
+  /**
+   * Checks if a node has children.
+   * @param node The node to check.
+   * @returns `true` if the node has children, `false` otherwise.
+   */
   private hasChildren(node?: ScopeChainNode): boolean {
     return Boolean(this.tree && node && this.tree.getChildren(node).length > 0);
   }
 
-  // 子节点按照顺序进行排序(含自身)
+  /**
+   * Gets all sorted child scopes of a node.
+   * @param node The node to get child scopes for.
+   * @param options Options for getting child scopes.
+   * @returns An array of sorted child scopes.
+   */
   private getAllSortedChildScope(
     node: ScopeChainNode,
     {
@@ -285,8 +317,8 @@ export class FixedLayoutScopeChain extends ScopeChain {
       scopes.push(variableData.public);
     }
 
-    // 私有作用域,子节点的变量不对外输出
-    //(父节点如果存在 public 变量则对外输出)
+    // For private scopes, the variables of child nodes are not exposed externally
+    // (If the parent node has public variables, they are exposed externally)
     if (ignoreNodeChildrenPrivate && this.isNodeChildrenPrivate(node)) {
       return scopes;
     }

+ 54 - 15
packages/variable-engine/variable-layout/src/chains/free-layout-scope-chain.ts

@@ -21,7 +21,7 @@ import { GlobalScope } from '../scopes/global-scope';
 import { FlowNodeVariableData } from '../flow-node-variable-data';
 
 /**
- * 自由布局作用域链实现
+ * Scope chain implementation for free layout.
  */
 export class FreeLayoutScopeChain extends ScopeChain {
   @inject(EntityManager) entityManager: EntityManager;
@@ -36,6 +36,9 @@ export class FreeLayoutScopeChain extends ScopeChain {
   @inject(ScopeChainTransformService)
   protected transformService: ScopeChainTransformService;
 
+  /**
+   * The virtual tree of the flow document.
+   */
   get tree(): FlowVirtualTree<FlowNodeEntity> {
     return this.flowDocument.originTree;
   }
@@ -43,20 +46,24 @@ export class FreeLayoutScopeChain extends ScopeChain {
   @postConstruct()
   onInit() {
     this.toDispose.pushAll([
-      // 线条发生变化时,会触发作用域链的更新
+      // When the line changes, the scope chain will be updated
       this.entityManager.onEntityDataChange(({ entityDataType }) => {
         if (entityDataType === WorkflowNodeLinesData.type) {
           this.refreshAllChange();
         }
       }),
-      // 树变化时候刷新作用域
+      // Refresh the scope when the tree changes
       this.tree.onTreeChange(() => {
         this.refreshAllChange();
       }),
     ]);
   }
 
-  // 获取同一层级所有输入节点, 按照由近到远的顺序
+  /**
+   * Gets all input layer nodes for a given node in the same layer, sorted by distance.
+   * @param node The node to get input layer nodes for.
+   * @returns An array of input layer nodes.
+   */
   protected getAllInputLayerNodes(node: FlowNodeEntity): FlowNodeEntity[] {
     const currParent = this.getNodeParent(node);
 
@@ -82,7 +89,11 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return Array.from(result).reverse();
   }
 
-  // 获取同一层级所有输出节点
+  /**
+   * Gets all output layer nodes for a given node in the same layer.
+   * @param curr The node to get output layer nodes for.
+   * @returns An array of output layer nodes.
+   */
   protected getAllOutputLayerNodes(curr: FlowNodeEntity): FlowNodeEntity[] {
     const currParent = this.getNodeParent(curr);
 
@@ -91,6 +102,11 @@ export class FreeLayoutScopeChain extends ScopeChain {
     );
   }
 
+  /**
+   * Gets the dependency scopes for a given scope.
+   * @param scope The scope to get dependencies for.
+   * @returns An array of dependency scopes.
+   */
   getDeps(scope: FlowNodeScope): FlowNodeScope[] {
     const { node } = scope.meta || {};
     if (!node) {
@@ -135,6 +151,11 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return this.transformService.transformDeps(uniqDeps, { scope });
   }
 
+  /**
+   * Gets the covering scopes for a given scope.
+   * @param scope The scope to get covering scopes for.
+   * @returns An array of covering scopes.
+   */
   getCovers(scope: FlowNodeScope): FlowNodeScope[] {
     // If scope is GlobalScope, return all scopes except GlobalScope
     if (GlobalScope.is(scope)) {
@@ -152,14 +173,14 @@ export class FreeLayoutScopeChain extends ScopeChain {
 
     const isPrivate = scope.meta.type === FlowNodeScopeTypeEnum.private;
 
-    // 1. BFS 找到所有覆盖的节点
+    // 1. BFS to find all covered nodes
     const queue: FlowNodeEntity[] = [];
 
     if (isPrivate) {
-      // private 只能覆盖其子节点
+      // private can only cover its child nodes
       queue.push(...this.getNodeChildren(node));
     } else {
-      // 否则覆盖其所有输出线的节点
+      // Otherwise, cover all nodes of its output lines
       queue.push(...(this.getAllOutputLayerNodes(node) || []));
 
       // get all parents
@@ -177,7 +198,7 @@ export class FreeLayoutScopeChain extends ScopeChain {
       }
     }
 
-    // 2. 获取所有覆盖节点的 public、private 作用域
+    // 2. Get the public and private scopes of all covered nodes
     const scopes: FlowNodeScope[] = [];
 
     while (queue.length) {
@@ -191,7 +212,7 @@ export class FreeLayoutScopeChain extends ScopeChain {
       }
     }
 
-    // 3. 如果当前 scope 是 private,则当前节点的 public 也可覆盖
+    // 3. If the current scope is private, the public scope of the current node can also be covered
     const currentVariableData: FlowNodeVariableData = node.getData(FlowNodeVariableData);
     if (isPrivate && currentVariableData.public) {
       scopes.push(currentVariableData.public);
@@ -202,6 +223,11 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return this.transformService.transformCovers(uniqScopes, { scope });
   }
 
+  /**
+   * Gets the children of a node.
+   * @param node The node to get children for.
+   * @returns An array of child nodes.
+   */
   getNodeChildren(node: FlowNodeEntity): FlowNodeEntity[] {
     if (this.configs?.getNodeChildren) {
       return this.configs.getNodeChildren?.(node);
@@ -210,7 +236,7 @@ export class FreeLayoutScopeChain extends ScopeChain {
     const subCanvas = nodeMeta.subCanvas?.(node);
 
     if (subCanvas) {
-      // 子画布本身不存在 children
+      // The sub-canvas itself does not have children
       if (subCanvas.isCanvas) {
         return [];
       } else {
@@ -218,7 +244,7 @@ export class FreeLayoutScopeChain extends ScopeChain {
       }
     }
 
-    // 部分场景通过连线来表达父子关系,因此需要上层配置
+    // In some scenarios, the parent-child relationship is expressed through connections, so it needs to be configured at the upper level
     return this.tree.getChildren(node);
   }
 
@@ -240,8 +266,13 @@ export class FreeLayoutScopeChain extends ScopeChain {
       .flat();
   }
 
+  /**
+   * Gets the parent of a node.
+   * @param node The node to get the parent for.
+   * @returns The parent node or `undefined` if not found.
+   */
   getNodeParent(node: FlowNodeEntity): FlowNodeEntity | undefined {
-    // 部分场景通过连线来表达父子关系,因此需要上层配置
+    // In some scenarios, the parent-child relationship is expressed through connections, so it needs to be configured at the upper level
     if (this.configs?.getNodeParent) {
       return this.configs.getNodeParent(node);
     }
@@ -266,7 +297,11 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return parent;
   }
 
-  // Child nodes can not be accessed
+  /**
+   * Checks if the children of a node are private and cannot be accessed by subsequent nodes.
+   * @param node The node to check.
+   * @returns `true` if the children are private, `false` otherwise.
+   */
   protected isNodeChildrenPrivate(node?: FlowNodeEntity): boolean {
     if (this.configs?.isNodeChildrenPrivate) {
       return node ? this.configs?.isNodeChildrenPrivate(node) : false;
@@ -278,8 +313,12 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return !isSystemNode && node?.flowNodeType !== FlowNodeBaseType.GROUP;
   }
 
+  /**
+   * Sorts all scopes in the scope chain.
+   * @returns An empty array, as this method is not implemented.
+   */
   sortAll(): Scope[] {
-    // 暂未实现
+    // Not implemented yet
     console.warn('FreeLayoutScopeChain.sortAll is not implemented');
     return [];
   }

+ 17 - 0
packages/variable-engine/variable-layout/src/flow-node-variable-data.ts

@@ -14,6 +14,9 @@ interface Options {
   variableEngine: VariableEngine;
 }
 
+/**
+ * Manages variable data for a flow node, including public and private scopes.
+ */
 export class FlowNodeVariableData extends EntityData {
   static type: string = 'FlowNodeVariableData';
 
@@ -28,10 +31,16 @@ export class FlowNodeVariableData extends EntityData {
 
   protected _public: FlowNodeScope;
 
+  /**
+   * The private scope of the node.
+   */
   get private() {
     return this._private;
   }
 
+  /**
+   * The public scope of the node.
+   */
   get public() {
     return this._public;
   }
@@ -134,6 +143,9 @@ export class FlowNodeVariableData extends EntityData {
     return this.private?.ast.remove(key);
   }
 
+  /**
+   * An array containing all scopes (public and private) of the node.
+   */
   get allScopes(): FlowNodeScope[] {
     const res = [];
 
@@ -163,6 +175,11 @@ export class FlowNodeVariableData extends EntityData {
     this.toDispose.push(this._public);
   }
 
+  /**
+   * Initializes and returns the private scope for the node.
+   * If the private scope already exists, it returns the existing one.
+   * @returns The private scope of the node.
+   */
   initPrivate(): FlowNodeScope {
     if (!this._private) {
       this._private = this.variableEngine.createScope(`${this.entity.id}_private`, {

+ 11 - 0
packages/variable-engine/variable-layout/src/scopes/global-scope.ts

@@ -6,10 +6,21 @@
 import { injectable, interfaces } from 'inversify';
 import { Scope, VariableEngine } from '@flowgram.ai/variable-core';
 
+/**
+ * Global Scope stores all variables that are not scoped to any node.
+ *
+ * - Variables in Global Scope can be accessed by any node.
+ * - Any other scope's variables can not be accessed by Global Scope.
+ */
 @injectable()
 export class GlobalScope extends Scope {
   static readonly ID = Symbol('GlobalScope');
 
+  /**
+   * Check if the scope is Global Scope.
+   * @param scope
+   * @returns
+   */
   static is(scope: Scope) {
     return scope.id === GlobalScope.ID;
   }

+ 34 - 1
packages/variable-engine/variable-layout/src/services/scope-chain-transform-service.ts

@@ -11,16 +11,37 @@ import { lazyInject } from '@flowgram.ai/core';
 import { VariableChainConfig } from '../variable-chain-config';
 import { FlowNodeScope } from '../types';
 
+/**
+ * Context for scope transformers.
+ */
 export interface TransformerContext {
+  /**
+   * The current scope
+   */
   scope: FlowNodeScope;
+  /**
+   * The flow document.
+   */
   document: FlowDocument;
+  /**
+   * The variable engine.
+   */
   variableEngine: VariableEngine;
 }
 
+/**
+ * A function that transforms an array of scopes.
+ * @param scopes The array of scopes to transform.
+ * @param ctx The transformer context.
+ * @returns The transformed array of scopes.
+ */
 export type IScopeTransformer = (scopes: Scope[], ctx: TransformerContext) => Scope[];
 
 const passthrough: IScopeTransformer = (scopes, ctx) => scopes;
 
+/**
+ * A service for transforming scope chains.
+ */
 @injectable()
 export class ScopeChainTransformService {
   protected transformerMap: Map<
@@ -57,7 +78,7 @@ export class ScopeChainTransformService {
   /**
    * register new transform function
    * @param transformerId used to identify transformer, prevent duplicated transformer
-   * @param transformer
+   * @param transformer The transformer to register.
    */
   registerTransformer(
     transformerId: string,
@@ -69,6 +90,12 @@ export class ScopeChainTransformService {
     this.transformerMap.set(transformerId, transformer);
   }
 
+  /**
+   * Transforms the dependency scopes.
+   * @param scopes The array of scopes to transform.
+   * @param param1 The context for the transformation.
+   * @returns The transformed array of scopes.
+   */
   transformDeps(scopes: Scope[], { scope }: { scope: Scope }): Scope[] {
     return Array.from(this.transformerMap.values()).reduce((scopes, transformer) => {
       if (!transformer.transformDeps) {
@@ -84,6 +111,12 @@ export class ScopeChainTransformService {
     }, scopes);
   }
 
+  /**
+   * Transforms the cover scopes.
+   * @param scopes The array of scopes to transform.
+   * @param param1 The context for the transformation.
+   * @returns The transformed array of scopes.
+   */
   transformCovers(scopes: Scope[], { scope }: { scope: Scope }): Scope[] {
     return Array.from(this.transformerMap.values()).reduce((scopes, transformer) => {
       if (!transformer.transformCovers) {

+ 33 - 1
packages/variable-engine/variable-layout/src/types.ts

@@ -6,22 +6,54 @@
 import { Scope } from '@flowgram.ai/variable-core';
 import { FlowNodeEntity } from '@flowgram.ai/document';
 
+/**
+ * Enum for flow node scope types.
+ */
 export enum FlowNodeScopeTypeEnum {
+  /**
+   * Public scope.
+   */
   public = 'public',
+  /**
+   * Private scope.
+   */
   private = 'private',
 }
 
+/**
+ * Metadata for a flow node scope.
+ */
 export interface FlowNodeScopeMeta {
+  /**
+   * The flow node entity associated with the scope.
+   */
   node?: FlowNodeEntity;
+  /**
+   * The type of the scope.
+   */
   type?: FlowNodeScopeTypeEnum;
 }
 
+/**
+ * Represents a virtual node in the scope chain.
+ */
 export interface ScopeVirtualNode {
+  /**
+   * The ID of the virtual node.
+   */
   id: string;
+  /**
+   * The type of the flow node.
+   */
   flowNodeType: 'virtualNode';
 }
 
+/**
+ * Represents a node in the scope chain, which can be either a flow node entity or a virtual node.
+ */
 export type ScopeChainNode = FlowNodeEntity | ScopeVirtualNode;
 
-// 节点内部的作用域
+/**
+ * Represents a scope associated with a flow node.
+ */
 export interface FlowNodeScope extends Scope<FlowNodeScopeMeta> {}

+ 6 - 4
packages/variable-engine/variable-layout/src/utils.ts

@@ -8,18 +8,20 @@ import { FlowNodeEntity } from '@flowgram.ai/document';
 import { FlowNodeVariableData } from './flow-node-variable-data';
 
 /**
- * Use `node.scope` instead
+ * Use `node.scope` instead.
  * @deprecated
- * @param node
+ * @param node The flow node entity.
+ * @returns The public scope of the node.
  */
 export function getNodeScope(node: FlowNodeEntity) {
   return node.getData(FlowNodeVariableData).public;
 }
 
 /**
- * Use `node.privateScope` instead
+ * Use `node.privateScope` instead.
  * @deprecated
- * @param node
+ * @param node The flow node entity.
+ * @returns The private scope of the node.
  */
 export function getNodePrivateScope(node: FlowNodeEntity) {
   return node.getData(FlowNodeVariableData).initPrivate();

+ 5 - 2
packages/variable-engine/variable-layout/src/variable-chain-config.ts

@@ -8,6 +8,9 @@ import { FlowNodeEntity } from '@flowgram.ai/document';
 import { type ScopeChainNode } from './types';
 import { IScopeTransformer } from './services/scope-chain-transform-service';
 
+/**
+ * Configuration for the variable chain.
+ */
 export interface VariableChainConfig {
   /**
    * The output variables of a node's children cannot be accessed by subsequent nodes.
@@ -25,12 +28,12 @@ export interface VariableChainConfig {
   getNodeParent?: (node: FlowNodeEntity) => FlowNodeEntity | undefined;
 
   /**
-   * Fine-tune the dependency scope
+   * Fine-tune the dependency scope.
    */
   transformDeps?: IScopeTransformer;
 
   /**
-   * 对依赖作用域进行微调
+   * Fine-tune the cover scope.
    */
   transformCovers?: IScopeTransformer;
 }