Преглед изворни кода

feat(variable): isNodeChildrenPrivate (#472)

Yiwei Mao пре 6 месеци
родитељ
комит
fa15044ed1

+ 5 - 0
apps/demo-free-layout/src/hooks/use-editor-props.tsx

@@ -179,6 +179,11 @@ export function useEditorProps(
        */
        */
       variableEngine: {
       variableEngine: {
         enable: true,
         enable: true,
+        chainConfig: {
+          isNodeChildrenPrivate(node) {
+            return false;
+          },
+        },
       },
       },
       /**
       /**
        * Redo/Undo enable
        * Redo/Undo enable

+ 21 - 8
packages/plugins/variable-plugin/src/create-variable-plugin.ts

@@ -7,7 +7,7 @@ import {
   FlowNodeVariableData,
   FlowNodeVariableData,
   FreeLayoutScopeChain,
   FreeLayoutScopeChain,
   FixedLayoutScopeChain,
   FixedLayoutScopeChain,
-  VariableLayoutConfig,
+  VariableChainConfig,
   bindGlobalScope,
   bindGlobalScope,
   ScopeChainTransformService,
   ScopeChainTransformService,
 } from '@flowgram.ai/variable-layout';
 } from '@flowgram.ai/variable-layout';
@@ -29,17 +29,27 @@ type Injector = (ctx: PluginContext) => Record<string, any>;
 
 
 export interface VariablePluginOptions {
 export interface VariablePluginOptions {
   enable?: boolean;
   enable?: boolean;
-  // 业务扩展 AST 节点
+  /**
+   * Custom Extends ASTNode
+   */
   extendASTNodes?: (ASTNodeRegistry | [ASTNodeRegistry] | [ASTNodeRegistry, Injector])[];
   extendASTNodes?: (ASTNodeRegistry | [ASTNodeRegistry] | [ASTNodeRegistry, Injector])[];
-  // 布局方式
+  /**
+   * Layout method
+   */
   layout?: 'fixed' | 'free';
   layout?: 'fixed' | 'free';
-  // 布局配置
-  layoutConfig?: VariableLayoutConfig;
+  /**
+   * @deprecated use chainConfig instead
+   */
+  layoutConfig?: VariableChainConfig;
+  /**
+   * Configuration for scope chain
+   */
+  chainConfig?: VariableChainConfig;
 }
 }
 
 
 export const createVariablePlugin = definePluginCreator<VariablePluginOptions>({
 export const createVariablePlugin = definePluginCreator<VariablePluginOptions>({
   onBind({ bind }, opts) {
   onBind({ bind }, opts) {
-    const { layout, layoutConfig } = opts;
+    const { layout, layoutConfig, chainConfig } = opts;
 
 
     bind(ScopeChainTransformService).toSelf().inSingletonScope();
     bind(ScopeChainTransformService).toSelf().inSingletonScope();
 
 
@@ -49,8 +59,11 @@ export const createVariablePlugin = definePluginCreator<VariablePluginOptions>({
     if (layout === 'fixed') {
     if (layout === 'fixed') {
       bind(ScopeChain).to(FixedLayoutScopeChain).inSingletonScope();
       bind(ScopeChain).to(FixedLayoutScopeChain).inSingletonScope();
     }
     }
-    if (layoutConfig) {
-      bind(VariableLayoutConfig).toConstantValue(layoutConfig || {});
+    if (chainConfig) {
+      bind(VariableChainConfig).toConstantValue(chainConfig || {});
+    } else if (layoutConfig) {
+      console.warn(`Layout Config deprecated, use chainConfig instead`);
+      bind(VariableChainConfig).toConstantValue(layoutConfig || {});
     }
     }
 
 
     bindGlobalScope(bind);
     bindGlobalScope(bind);

+ 3 - 3
packages/variable-engine/variable-layout/__mocks__/container.ts

@@ -14,7 +14,7 @@ import {
   FreeLayoutScopeChain,
   FreeLayoutScopeChain,
   FixedLayoutScopeChain,
   FixedLayoutScopeChain,
   FlowNodeVariableData,
   FlowNodeVariableData,
-  VariableLayoutConfig,
+  VariableChainConfig,
   GlobalScope,
   GlobalScope,
   bindGlobalScope,
   bindGlobalScope,
   ScopeChainTransformService,
   ScopeChainTransformService,
@@ -27,7 +27,7 @@ import {
 } from '@flowgram.ai/document';
 } from '@flowgram.ai/document';
 import { WorkflowDocumentContainerModule, WorkflowLinesManager, WorkflowSimpleLineContribution } from '@flowgram.ai/free-layout-core';
 import { WorkflowDocumentContainerModule, WorkflowLinesManager, WorkflowSimpleLineContribution } from '@flowgram.ai/free-layout-core';
 
 
-export interface TestConfig extends VariableLayoutConfig {
+export interface TestConfig extends VariableChainConfig {
   enableGlobalScope?: boolean;
   enableGlobalScope?: boolean;
   onInit?: (container: Container) => void;
   onInit?: (container: Container) => void;
   runExtraTest?: (container: Container) => void
   runExtraTest?: (container: Container) => void
@@ -47,7 +47,7 @@ export function getContainer(layout: 'free' | 'fixed', config?: TestConfig): Con
   }
   }
 
 
   if (layoutConfig) {
   if (layoutConfig) {
-    container.bind(VariableLayoutConfig).toConstantValue(layoutConfig);
+    container.bind(VariableChainConfig).toConstantValue(layoutConfig);
   }
   }
   if (layout === 'free') {
   if (layout === 'free') {
     container.bind(ScopeChain).to(FreeLayoutScopeChain).inSingletonScope();
     container.bind(ScopeChain).to(FreeLayoutScopeChain).inSingletonScope();

+ 351 - 0
packages/variable-engine/variable-layout/__tests__/__snapshots__/variable-free-is-node-children-private.test.ts.snap

@@ -0,0 +1,351 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Variable Free Layout Is Node Children Private > test get Covers 1`] = `
+Map {
+  "start_0" => [
+    "base_1",
+    "base_2",
+    "end_0",
+    "loop_1",
+    "base_3",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+  "end_0" => [],
+  "base_1" => [
+    "base_2",
+    "end_0",
+    "loop_1",
+    "base_3",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+  "base_2" => [
+    "end_0",
+  ],
+  "loop_1" => [
+    "base_3",
+    "end_0",
+  ],
+  "base_in_loop_1" => [
+    "base_in_loop_3",
+    "base_3",
+    "end_0",
+  ],
+  "base_in_loop_2" => [
+    "base_in_loop_3",
+    "base_3",
+    "end_0",
+  ],
+  "base_in_loop_3" => [
+    "base_3",
+    "end_0",
+  ],
+  "base_3" => [
+    "end_0",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test get Covers After Init Private 1`] = `
+Map {
+  "start_0" => [
+    "base_1",
+    "base_1_private",
+    "base_2",
+    "base_2_private",
+    "end_0",
+    "end_0_private",
+    "loop_1",
+    "loop_1_private",
+    "base_3",
+    "base_3_private",
+    "base_in_loop_1",
+    "base_in_loop_1_private",
+    "base_in_loop_2",
+    "base_in_loop_2_private",
+    "base_in_loop_3",
+    "base_in_loop_3_private",
+  ],
+  "end_0" => [],
+  "base_1" => [
+    "base_2",
+    "base_2_private",
+    "end_0",
+    "end_0_private",
+    "loop_1",
+    "loop_1_private",
+    "base_3",
+    "base_3_private",
+    "base_in_loop_1",
+    "base_in_loop_1_private",
+    "base_in_loop_2",
+    "base_in_loop_2_private",
+    "base_in_loop_3",
+    "base_in_loop_3_private",
+  ],
+  "base_2" => [
+    "end_0",
+    "end_0_private",
+  ],
+  "loop_1" => [
+    "base_3",
+    "base_3_private",
+    "end_0",
+    "end_0_private",
+  ],
+  "base_in_loop_1" => [
+    "base_in_loop_3",
+    "base_in_loop_3_private",
+    "base_3",
+    "base_3_private",
+    "end_0",
+    "end_0_private",
+  ],
+  "base_in_loop_2" => [
+    "base_in_loop_3",
+    "base_in_loop_3_private",
+    "base_3",
+    "base_3_private",
+    "end_0",
+    "end_0_private",
+  ],
+  "base_in_loop_3" => [
+    "base_3",
+    "base_3_private",
+    "end_0",
+    "end_0_private",
+  ],
+  "base_3" => [
+    "end_0",
+    "end_0_private",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test get Deps 1`] = `
+Map {
+  "start_0" => [],
+  "end_0" => [
+    "base_2",
+    "base_1",
+    "start_0",
+    "base_3",
+    "loop_1",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+  "base_1" => [
+    "start_0",
+  ],
+  "base_2" => [
+    "base_1",
+    "start_0",
+  ],
+  "loop_1" => [
+    "base_1",
+    "start_0",
+  ],
+  "base_in_loop_1" => [
+    "base_1",
+    "start_0",
+  ],
+  "base_in_loop_2" => [
+    "base_1",
+    "start_0",
+  ],
+  "base_in_loop_3" => [
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_1",
+    "start_0",
+  ],
+  "base_3" => [
+    "loop_1",
+    "base_1",
+    "start_0",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test get Deps After Init Private 1`] = `
+Map {
+  "start_0" => [
+    "start_0_private",
+  ],
+  "end_0" => [
+    "base_2",
+    "base_1",
+    "start_0",
+    "base_3",
+    "loop_1",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+    "end_0_private",
+  ],
+  "base_1" => [
+    "start_0",
+    "base_1_private",
+  ],
+  "base_2" => [
+    "base_1",
+    "start_0",
+    "base_2_private",
+  ],
+  "loop_1" => [
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_in_loop_1" => [
+    "base_in_loop_1_private",
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_in_loop_2" => [
+    "base_in_loop_2_private",
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_in_loop_3" => [
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3_private",
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_3" => [
+    "loop_1",
+    "base_1",
+    "start_0",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+    "base_3_private",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test get private scope Covers 1`] = `
+Map {
+  "start_0_private" => [
+    "start_0",
+  ],
+  "end_0_private" => [
+    "end_0",
+  ],
+  "base_1_private" => [
+    "base_1",
+  ],
+  "base_2_private" => [
+    "base_2",
+  ],
+  "loop_1_private" => [
+    "base_in_loop_1",
+    "base_in_loop_1_private",
+    "base_in_loop_2",
+    "base_in_loop_2_private",
+    "base_in_loop_3",
+    "base_in_loop_3_private",
+    "loop_1",
+  ],
+  "base_in_loop_1_private" => [
+    "base_in_loop_1",
+  ],
+  "base_in_loop_2_private" => [
+    "base_in_loop_2",
+  ],
+  "base_in_loop_3_private" => [
+    "base_in_loop_3",
+  ],
+  "base_3_private" => [
+    "base_3",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test get private scope Deps 1`] = `
+Map {
+  "start_0_private" => [],
+  "end_0_private" => [
+    "base_2",
+    "base_1",
+    "start_0",
+    "base_3",
+    "loop_1",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+  "base_1_private" => [
+    "start_0",
+  ],
+  "base_2_private" => [
+    "base_1",
+    "start_0",
+  ],
+  "loop_1_private" => [
+    "base_1",
+    "start_0",
+  ],
+  "base_in_loop_1_private" => [
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_in_loop_2_private" => [
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_in_loop_3_private" => [
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_1",
+    "start_0",
+    "loop_1_private",
+  ],
+  "base_3_private" => [
+    "loop_1",
+    "base_1",
+    "start_0",
+    "base_in_loop_1",
+    "base_in_loop_2",
+    "base_in_loop_3",
+  ],
+}
+`;
+
+exports[`Variable Free Layout Is Node Children Private > test sort 1`] = `
+[
+  "testScope",
+  "start_0",
+  "end_0",
+  "base_1",
+  "base_2",
+  "loop_1",
+  "base_in_loop_1",
+  "base_in_loop_2",
+  "base_in_loop_3",
+  "base_3",
+  "start_0_private",
+  "end_0_private",
+  "base_1_private",
+  "base_2_private",
+  "loop_1_private",
+  "base_in_loop_1_private",
+  "base_in_loop_2_private",
+  "base_in_loop_3_private",
+  "base_3_private",
+]
+`;

+ 0 - 108
packages/variable-engine/variable-layout/__tests__/__snapshots__/variable-free-layout-transform-empty.test.ts.snap

@@ -1,113 +1,5 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 
-exports[`Variable Free Layout > test get Covers 1`] = `
-Map {
-  "start_0" => [],
-  "end_0" => [],
-  "base_1" => [],
-  "base_2" => [],
-  "loop_1" => [],
-  "base_in_loop_1" => [],
-  "base_in_loop_2" => [],
-  "base_in_loop_3" => [],
-  "base_3" => [],
-}
-`;
-
-exports[`Variable Free Layout > test get Covers After Init Private 1`] = `
-Map {
-  "start_0" => [],
-  "end_0" => [],
-  "base_1" => [],
-  "base_2" => [],
-  "loop_1" => [],
-  "base_in_loop_1" => [],
-  "base_in_loop_2" => [],
-  "base_in_loop_3" => [],
-  "base_3" => [],
-}
-`;
-
-exports[`Variable Free Layout > test get Deps 1`] = `
-Map {
-  "start_0" => [],
-  "end_0" => [],
-  "base_1" => [],
-  "base_2" => [],
-  "loop_1" => [],
-  "base_in_loop_1" => [],
-  "base_in_loop_2" => [],
-  "base_in_loop_3" => [],
-  "base_3" => [],
-}
-`;
-
-exports[`Variable Free Layout > test get Deps After Init Private 1`] = `
-Map {
-  "start_0" => [],
-  "end_0" => [],
-  "base_1" => [],
-  "base_2" => [],
-  "loop_1" => [],
-  "base_in_loop_1" => [],
-  "base_in_loop_2" => [],
-  "base_in_loop_3" => [],
-  "base_3" => [],
-}
-`;
-
-exports[`Variable Free Layout > test get private scope Covers 1`] = `
-Map {
-  "start_0_private" => [],
-  "end_0_private" => [],
-  "base_1_private" => [],
-  "base_2_private" => [],
-  "loop_1_private" => [],
-  "base_in_loop_1_private" => [],
-  "base_in_loop_2_private" => [],
-  "base_in_loop_3_private" => [],
-  "base_3_private" => [],
-}
-`;
-
-exports[`Variable Free Layout > test get private scope Deps 1`] = `
-Map {
-  "start_0_private" => [],
-  "end_0_private" => [],
-  "base_1_private" => [],
-  "base_2_private" => [],
-  "loop_1_private" => [],
-  "base_in_loop_1_private" => [],
-  "base_in_loop_2_private" => [],
-  "base_in_loop_3_private" => [],
-  "base_3_private" => [],
-}
-`;
-
-exports[`Variable Free Layout > test sort 1`] = `
-[
-  "testScope",
-  "start_0",
-  "end_0",
-  "base_1",
-  "base_2",
-  "loop_1",
-  "base_in_loop_1",
-  "base_in_loop_2",
-  "base_in_loop_3",
-  "base_3",
-  "start_0_private",
-  "end_0_private",
-  "base_1_private",
-  "base_2_private",
-  "loop_1_private",
-  "base_in_loop_1_private",
-  "base_in_loop_2_private",
-  "base_in_loop_3_private",
-  "base_3_private",
-]
-`;
-
 exports[`Variable Free Layout transform empty > test get Covers 1`] = `
 exports[`Variable Free Layout transform empty > test get Covers 1`] = `
 Map {
 Map {
   "start_0" => [],
   "start_0" => [],

+ 13 - 0
packages/variable-engine/variable-layout/__tests__/variable-free-is-node-children-private.test.ts

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { runFreeLayoutTest } from '../__mocks__/run-free-layout-test';
+import { freeLayout1 } from '../__mocks__/free-layout-specs';
+
+runFreeLayoutTest('Variable Free Layout Is Node Children Private', freeLayout1, {
+  isNodeChildrenPrivate(node) {
+    return false;
+  },
+});

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

@@ -8,7 +8,7 @@ import { Scope, ScopeChain } from '@flowgram.ai/variable-core';
 import { FlowDocument, type FlowVirtualTree } from '@flowgram.ai/document';
 import { FlowDocument, type FlowVirtualTree } from '@flowgram.ai/document';
 import { FlowNodeEntity } from '@flowgram.ai/document';
 import { FlowNodeEntity } from '@flowgram.ai/document';
 
 
-import { VariableLayoutConfig } from '../variable-layout-config';
+import { VariableChainConfig } from '../variable-chain-config';
 import { FlowNodeScope, FlowNodeScopeTypeEnum, ScopeChainNode } from '../types';
 import { FlowNodeScope, FlowNodeScopeTypeEnum, ScopeChainNode } from '../types';
 import { ScopeChainTransformService } from '../services/scope-chain-transform-service';
 import { ScopeChainTransformService } from '../services/scope-chain-transform-service';
 import { GlobalScope } from '../scopes/global-scope';
 import { GlobalScope } from '../scopes/global-scope';
@@ -28,8 +28,8 @@ export class FixedLayoutScopeChain extends ScopeChain {
     @inject(FlowDocument)
     @inject(FlowDocument)
     protected flowDocument: FlowDocument,
     protected flowDocument: FlowDocument,
     @optional()
     @optional()
-    @inject(VariableLayoutConfig)
-    protected configs?: VariableLayoutConfig
+    @inject(VariableChainConfig)
+    protected configs?: VariableChainConfig
   ) {
   ) {
     super();
     super();
 
 
@@ -254,7 +254,7 @@ export class FixedLayoutScopeChain extends ScopeChain {
     return (node as FlowNodeEntity).getData(FlowNodeVariableData);
     return (node as FlowNodeEntity).getData(FlowNodeVariableData);
   }
   }
 
 
-  // privateScope:子节点不可以被后续节点访问
+  // 子节点不可以被后续节点访问
   private isNodeChildrenPrivate(node?: ScopeChainNode): boolean {
   private isNodeChildrenPrivate(node?: ScopeChainNode): boolean {
     if (this.configs?.isNodeChildrenPrivate) {
     if (this.configs?.isNodeChildrenPrivate) {
       return node ? this.configs?.isNodeChildrenPrivate(node) : false;
       return node ? this.configs?.isNodeChildrenPrivate(node) : false;

+ 53 - 6
packages/variable-engine/variable-layout/src/chains/free-layout-scope-chain.ts

@@ -14,7 +14,7 @@ import {
 } from '@flowgram.ai/document';
 } from '@flowgram.ai/document';
 import { EntityManager } from '@flowgram.ai/core';
 import { EntityManager } from '@flowgram.ai/core';
 
 
-import { VariableLayoutConfig } from '../variable-layout-config';
+import { VariableChainConfig } from '../variable-chain-config';
 import { FlowNodeScope, FlowNodeScopeTypeEnum } from '../types';
 import { FlowNodeScope, FlowNodeScopeTypeEnum } from '../types';
 import { ScopeChainTransformService } from '../services/scope-chain-transform-service';
 import { ScopeChainTransformService } from '../services/scope-chain-transform-service';
 import { GlobalScope } from '../scopes/global-scope';
 import { GlobalScope } from '../scopes/global-scope';
@@ -30,8 +30,8 @@ export class FreeLayoutScopeChain extends ScopeChain {
   protected flowDocument: FlowDocument;
   protected flowDocument: FlowDocument;
 
 
   @optional()
   @optional()
-  @inject(VariableLayoutConfig)
-  protected configs?: VariableLayoutConfig;
+  @inject(VariableChainConfig)
+  protected configs?: VariableChainConfig;
 
 
   @inject(ScopeChainTransformService)
   @inject(ScopeChainTransformService)
   protected transformService: ScopeChainTransformService;
   protected transformService: ScopeChainTransformService;
@@ -82,18 +82,21 @@ export class FreeLayoutScopeChain extends ScopeChain {
 
 
     const deps: FlowNodeScope[] = [];
     const deps: FlowNodeScope[] = [];
 
 
-    // 1. 找到依赖的节点
+    // 1. find dep nodes
     let curr: FlowNodeEntity | undefined = node;
     let curr: FlowNodeEntity | undefined = node;
 
 
     while (curr) {
     while (curr) {
       const allInputNodes: FlowNodeEntity[] = this.getAllInputLayerNodes(curr);
       const allInputNodes: FlowNodeEntity[] = this.getAllInputLayerNodes(curr);
 
 
-      // 2. 获取所有依赖节点的 public 作用域
+      // 2. all public scopes of inputNodes
       deps.push(
       deps.push(
         ...allInputNodes.map((_node) => _node.getData(FlowNodeVariableData).public).filter(Boolean)
         ...allInputNodes.map((_node) => _node.getData(FlowNodeVariableData).public).filter(Boolean)
       );
       );
 
 
-      // 父节点的 private 也可以访问
+      // 3. all public children of inputNodes
+      deps.push(...allInputNodes.map((_node) => this.getAllPublicChildScopes(_node)).flat());
+
+      // 4. private scope of parent node can be access
       const currVarData: FlowNodeVariableData = curr.getData(FlowNodeVariableData);
       const currVarData: FlowNodeVariableData = curr.getData(FlowNodeVariableData);
       if (currVarData?.private && scope !== currVarData.private) {
       if (currVarData?.private && scope !== currVarData.private) {
         deps.push(currVarData.private);
         deps.push(currVarData.private);
@@ -138,6 +141,20 @@ export class FreeLayoutScopeChain extends ScopeChain {
     } else {
     } else {
       // 否则覆盖其所有输出线的节点
       // 否则覆盖其所有输出线的节点
       queue.push(...(this.getAllOutputLayerNodes(node) || []));
       queue.push(...(this.getAllOutputLayerNodes(node) || []));
+
+      // get all parents
+      let parent = this.getNodeParent(node);
+
+      while (parent) {
+        // if childNodes of parent is private to next nodes, break
+        if (this.isNodeChildrenPrivate(parent)) {
+          break;
+        }
+
+        queue.push(...this.getAllOutputLayerNodes(parent));
+
+        parent = this.getNodeParent(parent);
+      }
     }
     }
 
 
     // 2. 获取所有覆盖节点的 public、private 作用域
     // 2. 获取所有覆盖节点的 public、private 作用域
@@ -185,6 +202,24 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return this.tree.getChildren(node);
     return this.tree.getChildren(node);
   }
   }
 
 
+  /**
+   * Get All children of nodes
+   * @param node
+   * @returns
+   */
+  getAllPublicChildScopes(node: FlowNodeEntity): Scope[] {
+    if (this.isNodeChildrenPrivate(node)) {
+      return [];
+    }
+
+    return this.getNodeChildren(node)
+      .map((_node) => [
+        _node.getData(FlowNodeVariableData).public,
+        ...this.getAllPublicChildScopes(_node),
+      ])
+      .flat();
+  }
+
   getNodeParent(node: FlowNodeEntity): FlowNodeEntity | undefined {
   getNodeParent(node: FlowNodeEntity): FlowNodeEntity | undefined {
     // 部分场景通过连线来表达父子关系,因此需要上层配置
     // 部分场景通过连线来表达父子关系,因此需要上层配置
     if (this.configs?.getNodeParent) {
     if (this.configs?.getNodeParent) {
@@ -211,6 +246,18 @@ export class FreeLayoutScopeChain extends ScopeChain {
     return parent;
     return parent;
   }
   }
 
 
+  // Child nodes can not be accessed
+  protected isNodeChildrenPrivate(node?: FlowNodeEntity): boolean {
+    if (this.configs?.isNodeChildrenPrivate) {
+      return node ? this.configs?.isNodeChildrenPrivate(node) : false;
+    }
+
+    const isSystemNode = node?.id.startsWith('$');
+
+    // Except system node and group node, everything else is private
+    return !isSystemNode && node?.flowNodeType !== FlowNodeBaseType.GROUP;
+  }
+
   sortAll(): Scope[] {
   sortAll(): Scope[] {
     // 暂未实现
     // 暂未实现
     console.warn('FreeLayoutScopeChain.sortAll is not implemented');
     console.warn('FreeLayoutScopeChain.sortAll is not implemented');

+ 1 - 1
packages/variable-engine/variable-layout/src/index.ts

@@ -5,7 +5,7 @@
 
 
 export { FlowNodeVariableData } from './flow-node-variable-data';
 export { FlowNodeVariableData } from './flow-node-variable-data';
 export { FreeLayoutScopeChain } from './chains/free-layout-scope-chain';
 export { FreeLayoutScopeChain } from './chains/free-layout-scope-chain';
-export { VariableLayoutConfig } from './variable-layout-config';
+export { VariableChainConfig } from './variable-chain-config';
 export { FixedLayoutScopeChain } from './chains/fixed-layout-scope-chain';
 export { FixedLayoutScopeChain } from './chains/fixed-layout-scope-chain';
 export {
 export {
   type FlowNodeScopeMeta,
   type FlowNodeScopeMeta,

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

@@ -8,7 +8,7 @@ import { Scope, VariableEngine } from '@flowgram.ai/variable-core';
 import { FlowDocument } from '@flowgram.ai/document';
 import { FlowDocument } from '@flowgram.ai/document';
 import { lazyInject } from '@flowgram.ai/core';
 import { lazyInject } from '@flowgram.ai/core';
 
 
-import { VariableLayoutConfig } from '../variable-layout-config';
+import { VariableChainConfig } from '../variable-chain-config';
 import { FlowNodeScope } from '../types';
 import { FlowNodeScope } from '../types';
 
 
 export interface TransformerContext {
 export interface TransformerContext {
@@ -34,8 +34,8 @@ export class ScopeChainTransformService {
 
 
   constructor(
   constructor(
     @optional()
     @optional()
-    @inject(VariableLayoutConfig)
-    protected configs?: VariableLayoutConfig
+    @inject(VariableChainConfig)
+    protected configs?: VariableChainConfig
   ) {
   ) {
     if (this.configs?.transformDeps || this.configs?.transformCovers) {
     if (this.configs?.transformDeps || this.configs?.transformCovers) {
       this.transformerMap.set('VARIABLE_LAYOUT_CONFIG', {
       this.transformerMap.set('VARIABLE_LAYOUT_CONFIG', {

+ 7 - 6
packages/variable-engine/variable-layout/src/variable-layout-config.ts → packages/variable-engine/variable-layout/src/variable-chain-config.ts

@@ -8,23 +8,24 @@ import { FlowNodeEntity } from '@flowgram.ai/document';
 import { type ScopeChainNode } from './types';
 import { type ScopeChainNode } from './types';
 import { IScopeTransformer } from './services/scope-chain-transform-service';
 import { IScopeTransformer } from './services/scope-chain-transform-service';
 
 
-export interface VariableLayoutConfig {
+export interface VariableChainConfig {
   /**
   /**
-   * 节点的子节点输出变量,不能被后续节点所访问,用于固定布局场景
+   * The output variables of a node's children cannot be accessed by subsequent nodes.
+   *
    * @param node
    * @param node
    * @returns
    * @returns
    */
    */
   isNodeChildrenPrivate?: (node: ScopeChainNode) => boolean;
   isNodeChildrenPrivate?: (node: ScopeChainNode) => boolean;
 
 
   /**
   /**
-   * 用于固定布局场景时:父子中间存在大量无用节点(如 inlineBlocks 等,需要配置化略过)
-   * 用于自由画布场景时:部分场景通过连线或者其他交互形式来表达节点之间的父子关系,需可配置化
+   * For fixed layout scenarios: there are a large number of useless nodes between parent and child (such as inlineBlocks, etc., which need to be configured to be skipped)
+   * For free canvas scenarios: in some scenarios, the parent-child relationship between nodes is expressed through connections or other interactive forms, which needs to be configurable
    */
    */
   getNodeChildren?: (node: FlowNodeEntity) => FlowNodeEntity[];
   getNodeChildren?: (node: FlowNodeEntity) => FlowNodeEntity[];
   getNodeParent?: (node: FlowNodeEntity) => FlowNodeEntity | undefined;
   getNodeParent?: (node: FlowNodeEntity) => FlowNodeEntity | undefined;
 
 
   /**
   /**
-   * 对依赖作用域进行微调
+   * Fine-tune the dependency scope
    */
    */
   transformDeps?: IScopeTransformer;
   transformDeps?: IScopeTransformer;
 
 
@@ -34,4 +35,4 @@ export interface VariableLayoutConfig {
   transformCovers?: IScopeTransformer;
   transformCovers?: IScopeTransformer;
 }
 }
 
 
-export const VariableLayoutConfig = Symbol('VariableLayoutConfig');
+export const VariableChainConfig = Symbol('VariableChainConfig');