@synergenius/flow-weaver 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +122 -0
- package/README.md +315 -0
- package/dist/annotation-generator.d.ts +45 -0
- package/dist/annotation-generator.js +557 -0
- package/dist/api/builder.d.ts +223 -0
- package/dist/api/builder.js +345 -0
- package/dist/api/compile.d.ts +92 -0
- package/dist/api/compile.js +149 -0
- package/dist/api/extract-types.d.ts +29 -0
- package/dist/api/extract-types.js +57 -0
- package/dist/api/generate-in-place.d.ts +73 -0
- package/dist/api/generate-in-place.js +1353 -0
- package/dist/api/generate.d.ts +83 -0
- package/dist/api/generate.js +510 -0
- package/dist/api/helpers.d.ts +248 -0
- package/dist/api/helpers.js +285 -0
- package/dist/api/index.d.ts +46 -0
- package/dist/api/index.js +45 -0
- package/dist/api/inline-runtime.d.ts +27 -0
- package/dist/api/inline-runtime.js +551 -0
- package/dist/api/manipulation/connections.d.ts +79 -0
- package/dist/api/manipulation/connections.js +151 -0
- package/dist/api/manipulation/index.d.ts +34 -0
- package/dist/api/manipulation/index.js +41 -0
- package/dist/api/manipulation/node-types.d.ts +123 -0
- package/dist/api/manipulation/node-types.js +200 -0
- package/dist/api/manipulation/nodes.d.ts +144 -0
- package/dist/api/manipulation/nodes.js +333 -0
- package/dist/api/manipulation/ports.d.ts +59 -0
- package/dist/api/manipulation/ports.js +228 -0
- package/dist/api/manipulation/scopes.d.ts +52 -0
- package/dist/api/manipulation/scopes.js +156 -0
- package/dist/api/manipulation/validation.d.ts +6 -0
- package/dist/api/manipulation/validation.js +6 -0
- package/dist/api/manipulation/workflow.d.ts +81 -0
- package/dist/api/manipulation/workflow.js +116 -0
- package/dist/api/manipulation.d.ts +8 -0
- package/dist/api/manipulation.js +8 -0
- package/dist/api/parse.d.ts +48 -0
- package/dist/api/parse.js +110 -0
- package/dist/api/patterns.d.ts +112 -0
- package/dist/api/patterns.js +306 -0
- package/dist/api/query.d.ts +429 -0
- package/dist/api/query.js +816 -0
- package/dist/api/templates.d.ts +98 -0
- package/dist/api/templates.js +117 -0
- package/dist/api/transform.d.ts +31 -0
- package/dist/api/transform.js +40 -0
- package/dist/api/validate.d.ts +25 -0
- package/dist/api/validate.js +39 -0
- package/dist/api/workflow-file-operations.d.ts +29 -0
- package/dist/api/workflow-file-operations.js +180 -0
- package/dist/ast/builder.d.ts +210 -0
- package/dist/ast/builder.js +395 -0
- package/dist/ast/index.d.ts +5 -0
- package/dist/ast/index.js +5 -0
- package/dist/ast/serialization-node.d.ts +6 -0
- package/dist/ast/serialization-node.js +30 -0
- package/dist/ast/serialization.d.ts +43 -0
- package/dist/ast/serialization.js +134 -0
- package/dist/ast/types.d.ts +852 -0
- package/dist/ast/types.js +2 -0
- package/dist/ast/workflow-utils.d.ts +54 -0
- package/dist/ast/workflow-utils.js +114 -0
- package/dist/body-generator.d.ts +31 -0
- package/dist/body-generator.js +35 -0
- package/dist/built-in-nodes/delay.d.ts +11 -0
- package/dist/built-in-nodes/delay.js +29 -0
- package/dist/built-in-nodes/index.d.ts +5 -0
- package/dist/built-in-nodes/index.js +4 -0
- package/dist/built-in-nodes/invoke-workflow.d.ts +13 -0
- package/dist/built-in-nodes/invoke-workflow.js +25 -0
- package/dist/built-in-nodes/mock-types.d.ts +18 -0
- package/dist/built-in-nodes/mock-types.js +12 -0
- package/dist/built-in-nodes/wait-for-event.d.ts +13 -0
- package/dist/built-in-nodes/wait-for-event.js +25 -0
- package/dist/chevrotain-parser/connect-parser.d.ts +24 -0
- package/dist/chevrotain-parser/connect-parser.js +98 -0
- package/dist/chevrotain-parser/grammar-diagrams.d.ts +29 -0
- package/dist/chevrotain-parser/grammar-diagrams.js +264 -0
- package/dist/chevrotain-parser/index.d.ts +25 -0
- package/dist/chevrotain-parser/index.js +27 -0
- package/dist/chevrotain-parser/map-parser.d.ts +33 -0
- package/dist/chevrotain-parser/map-parser.js +130 -0
- package/dist/chevrotain-parser/node-parser.d.ts +36 -0
- package/dist/chevrotain-parser/node-parser.js +466 -0
- package/dist/chevrotain-parser/path-parser.d.ts +28 -0
- package/dist/chevrotain-parser/path-parser.js +118 -0
- package/dist/chevrotain-parser/port-parser.d.ts +36 -0
- package/dist/chevrotain-parser/port-parser.js +442 -0
- package/dist/chevrotain-parser/position-parser.d.ts +20 -0
- package/dist/chevrotain-parser/position-parser.js +83 -0
- package/dist/chevrotain-parser/scope-parser.d.ts +19 -0
- package/dist/chevrotain-parser/scope-parser.js +104 -0
- package/dist/chevrotain-parser/tokens.d.ts +78 -0
- package/dist/chevrotain-parser/tokens.js +384 -0
- package/dist/chevrotain-parser/trigger-cancel-parser.d.ts +50 -0
- package/dist/chevrotain-parser/trigger-cancel-parser.js +282 -0
- package/dist/cli/commands/changelog.d.ts +13 -0
- package/dist/cli/commands/changelog.js +135 -0
- package/dist/cli/commands/compile.d.ts +64 -0
- package/dist/cli/commands/compile.js +278 -0
- package/dist/cli/commands/create.d.ts +33 -0
- package/dist/cli/commands/create.js +147 -0
- package/dist/cli/commands/describe.d.ts +68 -0
- package/dist/cli/commands/describe.js +377 -0
- package/dist/cli/commands/dev.d.ts +32 -0
- package/dist/cli/commands/dev.js +384 -0
- package/dist/cli/commands/diagram.d.ts +13 -0
- package/dist/cli/commands/diagram.js +33 -0
- package/dist/cli/commands/diff.d.ts +11 -0
- package/dist/cli/commands/diff.js +59 -0
- package/dist/cli/commands/doctor.d.ts +57 -0
- package/dist/cli/commands/doctor.js +719 -0
- package/dist/cli/commands/export.d.ts +57 -0
- package/dist/cli/commands/export.js +163 -0
- package/dist/cli/commands/grammar.d.ts +9 -0
- package/dist/cli/commands/grammar.js +39 -0
- package/dist/cli/commands/init.d.ts +59 -0
- package/dist/cli/commands/init.js +435 -0
- package/dist/cli/commands/listen.d.ts +16 -0
- package/dist/cli/commands/listen.js +39 -0
- package/dist/cli/commands/market.d.ts +52 -0
- package/dist/cli/commands/market.js +436 -0
- package/dist/cli/commands/migrate.d.ts +13 -0
- package/dist/cli/commands/migrate.js +89 -0
- package/dist/cli/commands/openapi.d.ts +37 -0
- package/dist/cli/commands/openapi.js +67 -0
- package/dist/cli/commands/pattern.d.ts +34 -0
- package/dist/cli/commands/pattern.js +185 -0
- package/dist/cli/commands/plugin.d.ts +16 -0
- package/dist/cli/commands/plugin.js +176 -0
- package/dist/cli/commands/run.d.ts +49 -0
- package/dist/cli/commands/run.js +191 -0
- package/dist/cli/commands/serve.d.ts +45 -0
- package/dist/cli/commands/serve.js +81 -0
- package/dist/cli/commands/templates.d.ts +8 -0
- package/dist/cli/commands/templates.js +54 -0
- package/dist/cli/commands/ui.d.ts +16 -0
- package/dist/cli/commands/ui.js +130 -0
- package/dist/cli/commands/validate.d.ts +12 -0
- package/dist/cli/commands/validate.js +247 -0
- package/dist/cli/commands/watch.d.ts +9 -0
- package/dist/cli/commands/watch.js +70 -0
- package/dist/cli/flow-weaver.mjs +92924 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +742 -0
- package/dist/cli/templates/ai/mock-provider.d.ts +7 -0
- package/dist/cli/templates/ai/mock-provider.js +64 -0
- package/dist/cli/templates/ai/types.d.ts +47 -0
- package/dist/cli/templates/ai/types.js +5 -0
- package/dist/cli/templates/approvals/index.d.ts +15 -0
- package/dist/cli/templates/approvals/index.js +241 -0
- package/dist/cli/templates/index.d.ts +102 -0
- package/dist/cli/templates/index.js +101 -0
- package/dist/cli/templates/nodes/agent-router.d.ts +3 -0
- package/dist/cli/templates/nodes/agent-router.js +114 -0
- package/dist/cli/templates/nodes/aggregator.d.ts +7 -0
- package/dist/cli/templates/nodes/aggregator.js +63 -0
- package/dist/cli/templates/nodes/conversation-memory.d.ts +3 -0
- package/dist/cli/templates/nodes/conversation-memory.js +85 -0
- package/dist/cli/templates/nodes/http.d.ts +7 -0
- package/dist/cli/templates/nodes/http.js +80 -0
- package/dist/cli/templates/nodes/human-approval.d.ts +3 -0
- package/dist/cli/templates/nodes/human-approval.js +110 -0
- package/dist/cli/templates/nodes/json-extractor.d.ts +3 -0
- package/dist/cli/templates/nodes/json-extractor.js +119 -0
- package/dist/cli/templates/nodes/llm-call.d.ts +3 -0
- package/dist/cli/templates/nodes/llm-call.js +106 -0
- package/dist/cli/templates/nodes/prompt-template.d.ts +3 -0
- package/dist/cli/templates/nodes/prompt-template.js +52 -0
- package/dist/cli/templates/nodes/rag-retriever.d.ts +3 -0
- package/dist/cli/templates/nodes/rag-retriever.js +128 -0
- package/dist/cli/templates/nodes/tool-executor.d.ts +3 -0
- package/dist/cli/templates/nodes/tool-executor.js +108 -0
- package/dist/cli/templates/nodes/transformer.d.ts +7 -0
- package/dist/cli/templates/nodes/transformer.js +68 -0
- package/dist/cli/templates/nodes/validator.d.ts +7 -0
- package/dist/cli/templates/nodes/validator.js +62 -0
- package/dist/cli/templates/providers/index.d.ts +14 -0
- package/dist/cli/templates/providers/index.js +239 -0
- package/dist/cli/templates/shared/approval-types.d.ts +9 -0
- package/dist/cli/templates/shared/approval-types.js +31 -0
- package/dist/cli/templates/shared/llm-types.d.ts +15 -0
- package/dist/cli/templates/shared/llm-types.js +104 -0
- package/dist/cli/templates/workflows/aggregator.d.ts +7 -0
- package/dist/cli/templates/workflows/aggregator.js +104 -0
- package/dist/cli/templates/workflows/ai-agent-durable.d.ts +8 -0
- package/dist/cli/templates/workflows/ai-agent-durable.js +338 -0
- package/dist/cli/templates/workflows/ai-agent.d.ts +31 -0
- package/dist/cli/templates/workflows/ai-agent.js +326 -0
- package/dist/cli/templates/workflows/ai-chat.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-chat.js +169 -0
- package/dist/cli/templates/workflows/ai-pipeline-durable.d.ts +8 -0
- package/dist/cli/templates/workflows/ai-pipeline-durable.js +330 -0
- package/dist/cli/templates/workflows/ai-rag.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-rag.js +186 -0
- package/dist/cli/templates/workflows/ai-react.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-react.js +294 -0
- package/dist/cli/templates/workflows/conditional.d.ts +12 -0
- package/dist/cli/templates/workflows/conditional.js +142 -0
- package/dist/cli/templates/workflows/error-handler.d.ts +7 -0
- package/dist/cli/templates/workflows/error-handler.js +147 -0
- package/dist/cli/templates/workflows/foreach.d.ts +7 -0
- package/dist/cli/templates/workflows/foreach.js +143 -0
- package/dist/cli/templates/workflows/sequential.d.ts +7 -0
- package/dist/cli/templates/workflows/sequential.js +198 -0
- package/dist/cli/templates/workflows/webhook.d.ts +7 -0
- package/dist/cli/templates/workflows/webhook.js +161 -0
- package/dist/cli/utils/logger.d.ts +15 -0
- package/dist/cli/utils/logger.js +46 -0
- package/dist/constants.d.ts +100 -0
- package/dist/constants.js +125 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.js +3 -0
- package/dist/deployment/config/defaults.d.ts +29 -0
- package/dist/deployment/config/defaults.js +98 -0
- package/dist/deployment/config/loader.d.ts +24 -0
- package/dist/deployment/config/loader.js +236 -0
- package/dist/deployment/config/types.d.ts +117 -0
- package/dist/deployment/config/types.js +5 -0
- package/dist/deployment/core/adapters.d.ts +90 -0
- package/dist/deployment/core/adapters.js +251 -0
- package/dist/deployment/core/executor.d.ts +62 -0
- package/dist/deployment/core/executor.js +197 -0
- package/dist/deployment/core/formatters.d.ts +57 -0
- package/dist/deployment/core/formatters.js +170 -0
- package/dist/deployment/index.d.ts +31 -0
- package/dist/deployment/index.js +48 -0
- package/dist/deployment/openapi/generator.d.ts +146 -0
- package/dist/deployment/openapi/generator.js +347 -0
- package/dist/deployment/openapi/schema-converter.d.ts +49 -0
- package/dist/deployment/openapi/schema-converter.js +192 -0
- package/dist/deployment/targets/base.d.ts +316 -0
- package/dist/deployment/targets/base.js +823 -0
- package/dist/deployment/targets/cloudflare.d.ts +23 -0
- package/dist/deployment/targets/cloudflare.js +1125 -0
- package/dist/deployment/targets/inngest.d.ts +38 -0
- package/dist/deployment/targets/inngest.js +926 -0
- package/dist/deployment/targets/lambda.d.ts +23 -0
- package/dist/deployment/targets/lambda.js +1289 -0
- package/dist/deployment/targets/vercel.d.ts +23 -0
- package/dist/deployment/targets/vercel.js +886 -0
- package/dist/deployment/types.d.ts +183 -0
- package/dist/deployment/types.js +8 -0
- package/dist/diagram/geometry.d.ts +26 -0
- package/dist/diagram/geometry.js +850 -0
- package/dist/diagram/index.d.ts +16 -0
- package/dist/diagram/index.js +42 -0
- package/dist/diagram/layout.d.ts +11 -0
- package/dist/diagram/layout.js +143 -0
- package/dist/diagram/orthogonal-router.d.ts +79 -0
- package/dist/diagram/orthogonal-router.js +568 -0
- package/dist/diagram/renderer.d.ts +3 -0
- package/dist/diagram/renderer.js +207 -0
- package/dist/diagram/theme.d.ts +20 -0
- package/dist/diagram/theme.js +189 -0
- package/dist/diagram/types.d.ts +70 -0
- package/dist/diagram/types.js +2 -0
- package/dist/diff/WorkflowDiffer.d.ts +13 -0
- package/dist/diff/WorkflowDiffer.js +429 -0
- package/dist/diff/formatDiff.d.ts +10 -0
- package/dist/diff/formatDiff.js +220 -0
- package/dist/diff/impact.d.ts +29 -0
- package/dist/diff/impact.js +119 -0
- package/dist/diff/index.d.ts +10 -0
- package/dist/diff/index.js +9 -0
- package/dist/diff/types.d.ts +138 -0
- package/dist/diff/types.js +35 -0
- package/dist/doc-metadata/extractors/annotations.d.ts +56 -0
- package/dist/doc-metadata/extractors/annotations.js +337 -0
- package/dist/doc-metadata/extractors/cli-commands.d.ts +17 -0
- package/dist/doc-metadata/extractors/cli-commands.js +355 -0
- package/dist/doc-metadata/extractors/mcp-tools.d.ts +16 -0
- package/dist/doc-metadata/extractors/mcp-tools.js +689 -0
- package/dist/doc-metadata/extractors/plugin-api.d.ts +19 -0
- package/dist/doc-metadata/extractors/plugin-api.js +279 -0
- package/dist/doc-metadata/index.d.ts +5 -0
- package/dist/doc-metadata/index.js +4 -0
- package/dist/doc-metadata/types.d.ts +120 -0
- package/dist/doc-metadata/types.js +5 -0
- package/dist/editor-completions/annotationValues.d.ts +12 -0
- package/dist/editor-completions/annotationValues.js +138 -0
- package/dist/editor-completions/contextParser.d.ts +40 -0
- package/dist/editor-completions/contextParser.js +410 -0
- package/dist/editor-completions/dataTypes.d.ts +16 -0
- package/dist/editor-completions/dataTypes.js +95 -0
- package/dist/editor-completions/goToDefinition.d.ts +27 -0
- package/dist/editor-completions/goToDefinition.js +112 -0
- package/dist/editor-completions/index.d.ts +39 -0
- package/dist/editor-completions/index.js +181 -0
- package/dist/editor-completions/jsDocAnnotations.d.ts +29 -0
- package/dist/editor-completions/jsDocAnnotations.js +357 -0
- package/dist/editor-completions/modifierCompletions.d.ts +17 -0
- package/dist/editor-completions/modifierCompletions.js +197 -0
- package/dist/editor-completions/types.d.ts +119 -0
- package/dist/editor-completions/types.js +8 -0
- package/dist/export/index.d.ts +68 -0
- package/dist/export/index.js +1074 -0
- package/dist/export/templates.d.ts +24 -0
- package/dist/export/templates.js +186 -0
- package/dist/friendly-errors.d.ts +35 -0
- package/dist/friendly-errors.js +375 -0
- package/dist/function-like.d.ts +38 -0
- package/dist/function-like.js +83 -0
- package/dist/generated-branding.d.ts +16 -0
- package/dist/generated-branding.js +22 -0
- package/dist/generator/async-detection.d.ts +27 -0
- package/dist/generator/async-detection.js +56 -0
- package/dist/generator/code-utils.d.ts +76 -0
- package/dist/generator/code-utils.js +410 -0
- package/dist/generator/control-flow.d.ts +54 -0
- package/dist/generator/control-flow.js +284 -0
- package/dist/generator/inngest.d.ts +53 -0
- package/dist/generator/inngest.js +1126 -0
- package/dist/generator/scope-function-generator.d.ts +78 -0
- package/dist/generator/scope-function-generator.js +360 -0
- package/dist/generator/unified.d.ts +42 -0
- package/dist/generator/unified.js +1504 -0
- package/dist/generator.d.ts +54 -0
- package/dist/generator.js +100 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +89 -0
- package/dist/jsdoc-parser.d.ts +308 -0
- package/dist/jsdoc-parser.js +923 -0
- package/dist/jsdoc-port-sync/constants.d.ts +41 -0
- package/dist/jsdoc-port-sync/constants.js +103 -0
- package/dist/jsdoc-port-sync/diff.d.ts +76 -0
- package/dist/jsdoc-port-sync/diff.js +319 -0
- package/dist/jsdoc-port-sync/index.d.ts +42 -0
- package/dist/jsdoc-port-sync/index.js +45 -0
- package/dist/jsdoc-port-sync/port-parser.d.ts +68 -0
- package/dist/jsdoc-port-sync/port-parser.js +579 -0
- package/dist/jsdoc-port-sync/rename.d.ts +21 -0
- package/dist/jsdoc-port-sync/rename.js +256 -0
- package/dist/jsdoc-port-sync/signature-parser.d.ts +104 -0
- package/dist/jsdoc-port-sync/signature-parser.js +559 -0
- package/dist/jsdoc-port-sync/sync.d.ts +36 -0
- package/dist/jsdoc-port-sync/sync.js +644 -0
- package/dist/jsdoc-port-sync.d.ts +10 -0
- package/dist/jsdoc-port-sync.js +10 -0
- package/dist/marketplace/index.d.ts +11 -0
- package/dist/marketplace/index.js +10 -0
- package/dist/marketplace/manifest.d.ts +32 -0
- package/dist/marketplace/manifest.js +176 -0
- package/dist/marketplace/registry.d.ts +30 -0
- package/dist/marketplace/registry.js +100 -0
- package/dist/marketplace/types.d.ts +154 -0
- package/dist/marketplace/types.js +9 -0
- package/dist/marketplace/validator.d.ts +13 -0
- package/dist/marketplace/validator.js +131 -0
- package/dist/mcp/auto-registration.d.ts +3 -0
- package/dist/mcp/auto-registration.js +62 -0
- package/dist/mcp/editor-connection.d.ts +50 -0
- package/dist/mcp/editor-connection.js +125 -0
- package/dist/mcp/event-buffer.d.ts +62 -0
- package/dist/mcp/event-buffer.js +150 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/resources.d.ts +14 -0
- package/dist/mcp/resources.js +55 -0
- package/dist/mcp/response-utils.d.ts +63 -0
- package/dist/mcp/response-utils.js +89 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +99 -0
- package/dist/mcp/tools-diagram.d.ts +8 -0
- package/dist/mcp/tools-diagram.js +53 -0
- package/dist/mcp/tools-editor.d.ts +5 -0
- package/dist/mcp/tools-editor.js +190 -0
- package/dist/mcp/tools-export.d.ts +9 -0
- package/dist/mcp/tools-export.js +180 -0
- package/dist/mcp/tools-marketplace.d.ts +9 -0
- package/dist/mcp/tools-marketplace.js +132 -0
- package/dist/mcp/tools-pattern.d.ts +3 -0
- package/dist/mcp/tools-pattern.js +783 -0
- package/dist/mcp/tools-query.d.ts +3 -0
- package/dist/mcp/tools-query.js +364 -0
- package/dist/mcp/tools-template.d.ts +10 -0
- package/dist/mcp/tools-template.js +119 -0
- package/dist/mcp/types.d.ts +70 -0
- package/dist/mcp/types.js +8 -0
- package/dist/mcp/workflow-executor.d.ts +47 -0
- package/dist/mcp/workflow-executor.js +133 -0
- package/dist/migration/registry.d.ts +30 -0
- package/dist/migration/registry.js +29 -0
- package/dist/node-types-generator.d.ts +49 -0
- package/dist/node-types-generator.js +139 -0
- package/dist/npm-packages.d.ts +56 -0
- package/dist/npm-packages.js +255 -0
- package/dist/parser.d.ts +204 -0
- package/dist/parser.js +2100 -0
- package/dist/plugin/PluginPanel.d.ts +12 -0
- package/dist/plugin/PluginPanel.js +5 -0
- package/dist/plugin/index.d.ts +13 -0
- package/dist/plugin/index.js +14 -0
- package/dist/plugin/types.d.ts +75 -0
- package/dist/plugin/types.js +8 -0
- package/dist/resolve-package-types.d.ts +17 -0
- package/dist/resolve-package-types.js +123 -0
- package/dist/runtime/CancellationError.d.ts +11 -0
- package/dist/runtime/CancellationError.js +20 -0
- package/dist/runtime/ExecutionContext.d.ts +146 -0
- package/dist/runtime/ExecutionContext.js +235 -0
- package/dist/runtime/builtin-functions.d.ts +8 -0
- package/dist/runtime/builtin-functions.js +549 -0
- package/dist/runtime/events.d.ts +50 -0
- package/dist/runtime/events.js +2 -0
- package/dist/runtime/function-registry.d.ts +59 -0
- package/dist/runtime/function-registry.js +66 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/parameter-resolver.d.ts +62 -0
- package/dist/runtime/parameter-resolver.js +113 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +6 -0
- package/dist/server/types.d.ts +93 -0
- package/dist/server/types.js +5 -0
- package/dist/server/webhook-server.d.ts +50 -0
- package/dist/server/webhook-server.js +269 -0
- package/dist/server/workflow-registry.d.ts +61 -0
- package/dist/server/workflow-registry.js +202 -0
- package/dist/shared-project.d.ts +9 -0
- package/dist/shared-project.js +28 -0
- package/dist/sugar-optimizer.d.ts +40 -0
- package/dist/sugar-optimizer.js +387 -0
- package/dist/testing/assertions.d.ts +51 -0
- package/dist/testing/assertions.js +127 -0
- package/dist/testing/index.d.ts +30 -0
- package/dist/testing/index.js +24 -0
- package/dist/testing/mock-approval.d.ts +81 -0
- package/dist/testing/mock-approval.js +98 -0
- package/dist/testing/mock-llm.d.ts +124 -0
- package/dist/testing/mock-llm.js +119 -0
- package/dist/testing/recorder.d.ts +72 -0
- package/dist/testing/recorder.js +70 -0
- package/dist/testing/replayer.d.ts +56 -0
- package/dist/testing/replayer.js +143 -0
- package/dist/testing/token-tracker.d.ts +71 -0
- package/dist/testing/token-tracker.js +94 -0
- package/dist/type-checker.d.ts +42 -0
- package/dist/type-checker.js +190 -0
- package/dist/type-mappings.d.ts +29 -0
- package/dist/type-mappings.js +125 -0
- package/dist/types/branded-ports.d.ts +151 -0
- package/dist/types/branded-ports.js +121 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +5 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +15 -0
- package/dist/utils/error-utils.d.ts +15 -0
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/lru-cache.d.ts +15 -0
- package/dist/utils/lru-cache.js +40 -0
- package/dist/utils/port-ordering.d.ts +26 -0
- package/dist/utils/port-ordering.js +88 -0
- package/dist/utils/port-tag-utils.d.ts +23 -0
- package/dist/utils/port-tag-utils.js +41 -0
- package/dist/utils/string-distance.d.ts +14 -0
- package/dist/utils/string-distance.js +56 -0
- package/dist/validation/agent-detection.d.ts +33 -0
- package/dist/validation/agent-detection.js +115 -0
- package/dist/validation/agent-rules.d.ts +48 -0
- package/dist/validation/agent-rules.js +262 -0
- package/dist/validator.d.ts +92 -0
- package/dist/validator.js +970 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1504 @@
|
|
|
1
|
+
import { extractStartPorts } from '../ast/workflow-utils.js';
|
|
2
|
+
import { mapToTypeScript } from '../type-mappings.js';
|
|
3
|
+
import { buildNodeArgumentsWithContext, toValidIdentifier } from './code-utils.js';
|
|
4
|
+
import { buildControlFlowGraph, detectBranchingChains, findAllBranchingNodes, findNodesInBranch, performKahnsTopologicalSort, isPerPortScopedChild, } from './control-flow.js';
|
|
5
|
+
import { RESERVED_NODE_NAMES, RESERVED_PORT_NAMES, EXECUTION_STRATEGIES, isStartNode, isExitNode, isExecutePort, isSuccessPort, isFailurePort, } from '../constants.js';
|
|
6
|
+
/**
|
|
7
|
+
* Helper: Determine if an instance has pull execution enabled
|
|
8
|
+
* Checks instance config first, then falls back to node type default config
|
|
9
|
+
* Returns { enabled: boolean, triggerPort: string }
|
|
10
|
+
*/
|
|
11
|
+
function getPullExecutionConfig(instance, nodeType) {
|
|
12
|
+
// Check instance config first
|
|
13
|
+
if (instance.config?.pullExecution) {
|
|
14
|
+
const pullConfig = instance.config.pullExecution;
|
|
15
|
+
if (typeof pullConfig === 'boolean') {
|
|
16
|
+
return { enabled: pullConfig, triggerPort: 'execute' };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
enabled: true,
|
|
20
|
+
triggerPort: pullConfig.triggerPort || 'execute',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Fall back to node type default config
|
|
24
|
+
if (nodeType.defaultConfig?.pullExecution) {
|
|
25
|
+
const pullConfig = nodeType.defaultConfig.pullExecution;
|
|
26
|
+
if (typeof pullConfig === 'boolean') {
|
|
27
|
+
return { enabled: pullConfig, triggerPort: 'execute' };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
enabled: true,
|
|
31
|
+
triggerPort: pullConfig.triggerPort || 'execute',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// No pull execution configured
|
|
35
|
+
return { enabled: false, triggerPort: 'execute' };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generates executable TypeScript code from a workflow AST using ExecutionContext for state management.
|
|
39
|
+
*
|
|
40
|
+
* This is the main code generation function that transforms a visual workflow into runnable code.
|
|
41
|
+
*
|
|
42
|
+
* ## Algorithm Overview:
|
|
43
|
+
* 1. **Setup Phase**: Initialize ExecutionContext, set recursion depth protection
|
|
44
|
+
* 2. **Start Node**: Store workflow parameters as Start node outputs
|
|
45
|
+
* 3. **Control Flow Graph**: Build CFG from connections, perform topological sort
|
|
46
|
+
* 4. **Branch Detection**: Identify branching nodes (with onSuccess/onFailure) and their regions
|
|
47
|
+
* 5. **Code Generation**: For each node in execution order:
|
|
48
|
+
* - Regular nodes: Generate direct execution with variable storage
|
|
49
|
+
* - Branching nodes: Generate if/else blocks for success/failure paths
|
|
50
|
+
* - Scoped children: Generate scope function closures (for forEach, etc.)
|
|
51
|
+
* - Pull nodes: Generate lazy executors registered with context
|
|
52
|
+
* 6. **Exit Node**: Collect outputs and return result object
|
|
53
|
+
*
|
|
54
|
+
* ## Key Concepts:
|
|
55
|
+
* - **Branching Nodes**: Nodes with both onSuccess and onFailure ports create conditional branches
|
|
56
|
+
* - **Per-Port Scoped Children**: Children of forEach-like nodes execute via closure functions
|
|
57
|
+
* - **Pull Execution**: Nodes marked for lazy evaluation only run when outputs are consumed
|
|
58
|
+
* - **Execution Index**: Each node execution gets a unique index for variable tracking
|
|
59
|
+
*
|
|
60
|
+
* ## Generated Code Structure:
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const ctx = new GeneratedExecutionContext(isAsync, debugger?);
|
|
63
|
+
* const startIdx = ctx.addExecution('Start');
|
|
64
|
+
* ctx.setVariable({ id: 'Start', portName: 'param', ... }, value);
|
|
65
|
+
* // ... node executions in topological order ...
|
|
66
|
+
* const exitIdx = ctx.addExecution('Exit');
|
|
67
|
+
* return { onSuccess: ..., onFailure: ..., ...outputs };
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @param workflow - The workflow AST to generate code for
|
|
71
|
+
* @param nodeTypes - Available node type definitions (includes workflow's nodeTypes + imports)
|
|
72
|
+
* @param isAsync - Whether to generate async code (adds await, returns Promise)
|
|
73
|
+
* @param production - If true, omits debug instrumentation for smaller output
|
|
74
|
+
* @returns Generated TypeScript function body (without function signature)
|
|
75
|
+
*/
|
|
76
|
+
export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isAsync, production = false, bundleMode = false) {
|
|
77
|
+
const lines = [];
|
|
78
|
+
// In development mode, determine effective debugger (from parameter or environment)
|
|
79
|
+
if (!production) {
|
|
80
|
+
lines.push(` // Use passed debugger or auto-detect from environment variable`);
|
|
81
|
+
lines.push(` const __effectiveDebugger__ = (`);
|
|
82
|
+
lines.push(` typeof __flowWeaverDebugger__ !== 'undefined' ? __flowWeaverDebugger__ :`);
|
|
83
|
+
lines.push(` typeof process !== 'undefined' && process.env.FLOW_WEAVER_DEBUG`);
|
|
84
|
+
lines.push(` ? createFlowWeaverDebugClient(process.env.FLOW_WEAVER_DEBUG, '${workflow.functionName}')`);
|
|
85
|
+
lines.push(` : undefined`);
|
|
86
|
+
lines.push(` );`);
|
|
87
|
+
lines.push('');
|
|
88
|
+
}
|
|
89
|
+
// Recursion depth protection: prevent infinite recursion in workflows
|
|
90
|
+
lines.push(` // Recursion depth protection`);
|
|
91
|
+
lines.push(` const __rd__ = (params as { __rd__?: number }).__rd__ ?? 0;`);
|
|
92
|
+
lines.push(` if (__rd__ >= 1000) {`);
|
|
93
|
+
lines.push(` throw new Error('Max recursion depth exceeded (1000) in workflow "${workflow.functionName}"');`);
|
|
94
|
+
lines.push(` }`);
|
|
95
|
+
lines.push('');
|
|
96
|
+
// In development mode, pass the effective debugger (from parameter or environment)
|
|
97
|
+
// In production mode, omit the debugger parameter
|
|
98
|
+
// Always pass abort signal for cancellation support
|
|
99
|
+
const asyncArg = isAsync ? 'true' : 'false';
|
|
100
|
+
if (production) {
|
|
101
|
+
lines.push(` const ctx = new GeneratedExecutionContext(${asyncArg}, __abortSignal__);`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push(` const ctx = new GeneratedExecutionContext(${asyncArg}, __effectiveDebugger__, __abortSignal__);`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push(` const startIdx = ctx.addExecution('${RESERVED_NODE_NAMES.START}');`);
|
|
108
|
+
Object.keys(extractStartPorts(workflow)).forEach((portName) => {
|
|
109
|
+
const setCall = isAsync ? `await ctx.setVariable` : `ctx.setVariable`;
|
|
110
|
+
// STEP Port Architecture: execute comes from workflow parameter, data from params object
|
|
111
|
+
const valueSource = isExecutePort(portName) ? 'execute' : `params.${portName}`;
|
|
112
|
+
lines.push(` ${setCall}({ id: '${RESERVED_NODE_NAMES.START}', portName: '${portName}', executionIndex: startIdx, nodeTypeName: '${RESERVED_NODE_NAMES.START}' }, ${valueSource});`);
|
|
113
|
+
});
|
|
114
|
+
lines.push(` ctx.sendStatusChangedEvent({`);
|
|
115
|
+
lines.push(` nodeTypeName: '${RESERVED_NODE_NAMES.START}',`);
|
|
116
|
+
lines.push(` id: '${RESERVED_NODE_NAMES.START}',`);
|
|
117
|
+
lines.push(` executionIndex: startIdx,`);
|
|
118
|
+
lines.push(` status: 'SUCCEEDED',`);
|
|
119
|
+
lines.push(` });`);
|
|
120
|
+
lines.push('');
|
|
121
|
+
const cfg = buildControlFlowGraph(workflow, nodeTypes);
|
|
122
|
+
const executionOrder = performKahnsTopologicalSort(cfg); // Now returns instance IDs
|
|
123
|
+
const branchingNodes = findAllBranchingNodes(workflow, nodeTypes);
|
|
124
|
+
const allInstanceIds = new Set(workflow.instances.map((i) => i.id));
|
|
125
|
+
const branchRegions = new Map();
|
|
126
|
+
branchingNodes.forEach((branchInstanceId) => {
|
|
127
|
+
const successNodes = findNodesInBranch(branchInstanceId, RESERVED_PORT_NAMES.ON_SUCCESS, workflow, allInstanceIds, branchingNodes, nodeTypes);
|
|
128
|
+
const failureNodes = findNodesInBranch(branchInstanceId, RESERVED_PORT_NAMES.ON_FAILURE, workflow, allInstanceIds, branchingNodes, nodeTypes);
|
|
129
|
+
branchRegions.set(branchInstanceId, { successNodes, failureNodes });
|
|
130
|
+
});
|
|
131
|
+
// Determine which nodes are in conditional branches (need let declaration)
|
|
132
|
+
// Nodes in branches may not execute, so we need undefined checks for them
|
|
133
|
+
const nodesInBranches = new Set();
|
|
134
|
+
branchRegions.forEach((region) => {
|
|
135
|
+
region.successNodes.forEach((n) => nodesInBranches.add(n));
|
|
136
|
+
region.failureNodes.forEach((n) => nodesInBranches.add(n));
|
|
137
|
+
});
|
|
138
|
+
// Identify pull execution nodes (they also need let due to undefined check)
|
|
139
|
+
const pullExecutionNodes = new Set();
|
|
140
|
+
workflow.instances.forEach((instance) => {
|
|
141
|
+
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
142
|
+
const nodeType = nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
143
|
+
if (nodeType) {
|
|
144
|
+
const pullConfig = getPullExecutionConfig(instance, nodeType);
|
|
145
|
+
if (pullConfig.enabled) {
|
|
146
|
+
pullExecutionNodes.add(instance.id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// Identify node-level scoped children (they need let because referenced outside scope block)
|
|
151
|
+
const nodeLevelScopedChildren = new Set();
|
|
152
|
+
workflow.instances.forEach((instance) => {
|
|
153
|
+
if (instance.parent && !isPerPortScopedChild(instance, workflow, nodeTypes)) {
|
|
154
|
+
nodeLevelScopedChildren.add(instance.id);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Create execution index variables only for nodes that need undefined checking
|
|
158
|
+
// Skip per-port scoped children (they're in scope functions)
|
|
159
|
+
let hasLetDeclarations = false;
|
|
160
|
+
workflow.instances.forEach((instance) => {
|
|
161
|
+
if (!isPerPortScopedChild(instance, workflow, nodeTypes)) {
|
|
162
|
+
// Nodes in branches, branching nodes, pull nodes, or scoped children need let
|
|
163
|
+
if (nodesInBranches.has(instance.id) ||
|
|
164
|
+
branchingNodes.has(instance.id) ||
|
|
165
|
+
pullExecutionNodes.has(instance.id) ||
|
|
166
|
+
nodeLevelScopedChildren.has(instance.id)) {
|
|
167
|
+
lines.push(` let ${toValidIdentifier(instance.id)}Idx: number | undefined;`);
|
|
168
|
+
hasLetDeclarations = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (hasLetDeclarations) {
|
|
173
|
+
lines.push('');
|
|
174
|
+
}
|
|
175
|
+
const instancesInMultipleBranches = new Set();
|
|
176
|
+
allInstanceIds.forEach((instanceId) => {
|
|
177
|
+
let branchCount = 0;
|
|
178
|
+
branchRegions.forEach((region) => {
|
|
179
|
+
if (region.successNodes.has(instanceId) || region.failureNodes.has(instanceId)) {
|
|
180
|
+
branchCount++;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (branchCount > 1) {
|
|
184
|
+
instancesInMultipleBranches.add(instanceId);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
branchRegions.forEach((region, _branchNode) => {
|
|
188
|
+
instancesInMultipleBranches.forEach((instanceId) => {
|
|
189
|
+
region.successNodes.delete(instanceId);
|
|
190
|
+
region.failureNodes.delete(instanceId);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
// Promote nodes that have DATA dependencies on nodes outside their branch.
|
|
194
|
+
// Without this, STEP-nesting places the node before its data providers are generated.
|
|
195
|
+
const nodesPromotedFromBranches = new Set();
|
|
196
|
+
branchRegions.forEach((region, branchNodeId) => {
|
|
197
|
+
const allBranchNodes = new Set([...region.successNodes, ...region.failureNodes]);
|
|
198
|
+
allBranchNodes.forEach((nodeId) => {
|
|
199
|
+
const hasExternalDataDep = workflow.connections.some((conn) => {
|
|
200
|
+
if (conn.to.node !== nodeId)
|
|
201
|
+
return false;
|
|
202
|
+
if (conn.from.scope || conn.to.scope)
|
|
203
|
+
return false;
|
|
204
|
+
const fromNode = conn.from.node;
|
|
205
|
+
// Dependencies on branch parent or Start are fine (already generated)
|
|
206
|
+
if (fromNode === branchNodeId || isStartNode(fromNode))
|
|
207
|
+
return false;
|
|
208
|
+
// STEP connections (execute port) are handled by the guard, not data flow
|
|
209
|
+
if (isExecutePort(conn.to.port))
|
|
210
|
+
return false;
|
|
211
|
+
// External dep: source is NOT in the same branch
|
|
212
|
+
return !allBranchNodes.has(fromNode);
|
|
213
|
+
});
|
|
214
|
+
if (hasExternalDataDep) {
|
|
215
|
+
nodesPromotedFromBranches.add(nodeId);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
// Remove promoted nodes from branch regions (they'll generate at top level)
|
|
220
|
+
branchRegions.forEach((region) => {
|
|
221
|
+
nodesPromotedFromBranches.forEach((nodeId) => {
|
|
222
|
+
region.successNodes.delete(nodeId);
|
|
223
|
+
region.failureNodes.delete(nodeId);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
// Identify branching nodes whose _success flag must be tracked because
|
|
227
|
+
// promoted nodes depend on their onSuccess/onFailure ports for STEP guards.
|
|
228
|
+
const branchingNodesNeedingSuccessFlag = new Set();
|
|
229
|
+
nodesPromotedFromBranches.forEach((promotedNodeId) => {
|
|
230
|
+
workflow.connections.forEach((conn) => {
|
|
231
|
+
if (conn.to.node === promotedNodeId && isExecutePort(conn.to.port)) {
|
|
232
|
+
const sourceNode = conn.from.node;
|
|
233
|
+
const sourcePort = conn.from.port;
|
|
234
|
+
if (branchingNodes.has(sourceNode) && (isSuccessPort(sourcePort) || isFailurePort(sourcePort))) {
|
|
235
|
+
branchingNodesNeedingSuccessFlag.add(sourceNode);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
// Detect sequential branching chains for flattening
|
|
241
|
+
const branchingChains = detectBranchingChains(branchingNodes, branchRegions);
|
|
242
|
+
const chainMembers = new Set();
|
|
243
|
+
branchingChains.forEach((chain) => {
|
|
244
|
+
// All non-head nodes are chain members (skip in main loop)
|
|
245
|
+
for (let i = 1; i < chain.length; i++) {
|
|
246
|
+
chainMembers.add(chain[i]);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const generatedNodes = new Set();
|
|
250
|
+
const availableVars = new Map();
|
|
251
|
+
Object.keys(extractStartPorts(workflow)).forEach((portName) => {
|
|
252
|
+
availableVars.set(`${RESERVED_NODE_NAMES.START}.${portName}`, `params.${portName}`);
|
|
253
|
+
});
|
|
254
|
+
executionOrder.forEach((instanceId) => {
|
|
255
|
+
if (isStartNode(instanceId) || isExitNode(instanceId) || generatedNodes.has(instanceId)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Find the instance and its node type
|
|
259
|
+
const instance = workflow.instances.find((i) => i.id === instanceId);
|
|
260
|
+
if (!instance) {
|
|
261
|
+
lines.push(` // Node '${instanceId}' skipped: instance not found in workflow`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Skip per-port scoped children (they're in scope functions)
|
|
265
|
+
// Include node-level scoped children (they're in scope blocks)
|
|
266
|
+
if (isPerPortScopedChild(instance, workflow, nodeTypes)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
270
|
+
const nodeType = nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
271
|
+
if (!nodeType) {
|
|
272
|
+
lines.push(` // Node '${instance.id}' skipped: type '${instance.nodeType}' not found`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (branchingNodes.has(instanceId)) {
|
|
276
|
+
// Chain members are generated by their chain head — skip
|
|
277
|
+
if (chainMembers.has(instanceId)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Chain heads: use flat chain generation
|
|
281
|
+
if (branchingChains.has(instanceId)) {
|
|
282
|
+
// For promoted chain heads, wrap in STEP guard
|
|
283
|
+
let chainIndent = ' ';
|
|
284
|
+
let chainNeedsClose = false;
|
|
285
|
+
if (nodesPromotedFromBranches.has(instanceId)) {
|
|
286
|
+
const stepSourceConditions = [];
|
|
287
|
+
workflow.connections.forEach((conn) => {
|
|
288
|
+
if (conn.to.node === instanceId && isExecutePort(conn.to.port)) {
|
|
289
|
+
const src = conn.from.node;
|
|
290
|
+
if (!isStartNode(src)) {
|
|
291
|
+
stepSourceConditions.push(buildStepSourceCondition(src, conn.from.port, branchingNodes));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
if (stepSourceConditions.length > 0) {
|
|
296
|
+
const condition = stepSourceConditions.join(' && ');
|
|
297
|
+
lines.push(` if (${condition}) {`);
|
|
298
|
+
chainIndent = ' ';
|
|
299
|
+
chainNeedsClose = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
generateBranchingChainCode(branchingChains.get(instanceId), workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, chainIndent, isAsync, 'ctx', bundleMode, branchingNodesNeedingSuccessFlag);
|
|
303
|
+
if (chainNeedsClose) {
|
|
304
|
+
lines.push(` }`);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// Non-chain branching nodes: existing path
|
|
309
|
+
// For promoted branching nodes, wrap in STEP guard from execute port source
|
|
310
|
+
let branchIndent = ' ';
|
|
311
|
+
let branchNeedsClose = false;
|
|
312
|
+
if (nodesPromotedFromBranches.has(instanceId)) {
|
|
313
|
+
const stepSourceConditions = [];
|
|
314
|
+
workflow.connections.forEach((conn) => {
|
|
315
|
+
if (conn.to.node === instanceId && isExecutePort(conn.to.port)) {
|
|
316
|
+
const src = conn.from.node;
|
|
317
|
+
if (!isStartNode(src)) {
|
|
318
|
+
stepSourceConditions.push(buildStepSourceCondition(src, conn.from.port, branchingNodes));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
if (stepSourceConditions.length > 0) {
|
|
323
|
+
const condition = stepSourceConditions.join(' && ');
|
|
324
|
+
lines.push(` if (${condition}) {`);
|
|
325
|
+
branchIndent = ' ';
|
|
326
|
+
branchNeedsClose = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, branchRegions.get(instanceId), availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, new Set(), branchingNodesNeedingSuccessFlag.has(instanceId));
|
|
330
|
+
if (branchNeedsClose) {
|
|
331
|
+
lines.push(` }`);
|
|
332
|
+
}
|
|
333
|
+
const region = branchRegions.get(instanceId);
|
|
334
|
+
region.successNodes.forEach((n) => generatedNodes.add(n));
|
|
335
|
+
region.failureNodes.forEach((n) => generatedNodes.add(n));
|
|
336
|
+
// Check if this node creates a scope and generate scoped children
|
|
337
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const belongsToBranch = Array.from(branchRegions.values()).some((region) => region.successNodes.has(instanceId) || region.failureNodes.has(instanceId));
|
|
341
|
+
if (!belongsToBranch) {
|
|
342
|
+
const nodeUseConst = !nodesInBranches.has(instanceId) &&
|
|
343
|
+
!branchingNodes.has(instanceId) &&
|
|
344
|
+
!pullExecutionNodes.has(instanceId) &&
|
|
345
|
+
!nodeLevelScopedChildren.has(instanceId);
|
|
346
|
+
generateNodeCallWithContext(instance, nodeType, workflow, availableVars, lines, nodeTypes, ' ', isAsync, nodeUseConst, undefined, // instanceParent
|
|
347
|
+
'ctx', // ctxVar
|
|
348
|
+
bundleMode, false, // skipExecuteGuard
|
|
349
|
+
branchingNodes // for port-aware STEP guards
|
|
350
|
+
);
|
|
351
|
+
generatedNodes.add(instanceId);
|
|
352
|
+
// Check if this node creates a scope and generate scoped children
|
|
353
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, ' ', branchingNodes, branchRegions, isAsync, bundleMode);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
lines.push(` const exitIdx = ctx.addExecution('${RESERVED_NODE_NAMES.EXIT}');`);
|
|
358
|
+
const exitConnections = workflow.connections.filter((conn) => isExitNode(conn.to.node));
|
|
359
|
+
// Group exit connections by port (multiple connections to the same port are coalesced)
|
|
360
|
+
const exitConnectionsByPort = new Map();
|
|
361
|
+
exitConnections.forEach((conn) => {
|
|
362
|
+
const existing = exitConnectionsByPort.get(conn.to.port) || [];
|
|
363
|
+
existing.push(conn);
|
|
364
|
+
exitConnectionsByPort.set(conn.to.port, existing);
|
|
365
|
+
});
|
|
366
|
+
const returnProps = [];
|
|
367
|
+
const awaitKeyword = isAsync ? 'await ' : '';
|
|
368
|
+
const setCall = isAsync ? 'await ctx.setVariable' : 'ctx.setVariable';
|
|
369
|
+
exitConnectionsByPort.forEach((conns, exitPort) => {
|
|
370
|
+
// Get exit port type for type casting - check if exit port is declared
|
|
371
|
+
const exitPortDef = workflow.exitPorts[exitPort];
|
|
372
|
+
// Skip connections to undeclared exit ports (typos like Exit.resultx when only @returns result exists)
|
|
373
|
+
if (!exitPortDef) {
|
|
374
|
+
lines.push(` // Exit connection skipped: '${exitPort}' is not a declared @returns port`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const varName = `exit_${exitPort}`;
|
|
378
|
+
const exitPortType = exitPortDef?.tsType || (exitPortDef ? mapToTypeScript(exitPortDef.dataType) : 'unknown');
|
|
379
|
+
// Filter to valid connections (skip undeclared nodes, missing types)
|
|
380
|
+
const validConns = conns.filter((conn) => {
|
|
381
|
+
const sourceNode = conn.from.node;
|
|
382
|
+
const sourceInstance = workflow.instances.find((i) => i.id === sourceNode);
|
|
383
|
+
const sourceNodeType = nodeTypes.find((n) => n.name === sourceInstance?.nodeType || n.functionName === sourceInstance?.nodeType);
|
|
384
|
+
if (!isStartNode(sourceNode) && !sourceInstance) {
|
|
385
|
+
lines.push(` // Exit connection skipped: source node '${sourceNode}' is not declared`);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (!isStartNode(sourceNode) && sourceInstance && !sourceNodeType) {
|
|
389
|
+
lines.push(` // Exit connection skipped: source node '${sourceNode}' has missing type '${sourceInstance.nodeType}'`);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
});
|
|
394
|
+
if (validConns.length === 0) {
|
|
395
|
+
lines.push(` const ${varName} = undefined as unknown;`);
|
|
396
|
+
returnProps.push(`${exitPort}: ${varName} as ${exitPortType}`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
// Helper to build a value expression for a single connection
|
|
400
|
+
const buildSourceExpr = (conn, defaultValue) => {
|
|
401
|
+
const sourceNode = conn.from.node;
|
|
402
|
+
const sourcePort = conn.from.port;
|
|
403
|
+
const sourceIdx = isStartNode(sourceNode) ? 'startIdx' : `${toValidIdentifier(sourceNode)}Idx`;
|
|
404
|
+
const sourceInstance = workflow.instances.find((i) => i.id === sourceNode);
|
|
405
|
+
const sourceNodeType = nodeTypes.find((n) => n.name === sourceInstance?.nodeType || n.functionName === sourceInstance?.nodeType);
|
|
406
|
+
const pullConfig = sourceInstance && sourceNodeType
|
|
407
|
+
? getPullExecutionConfig(sourceInstance, sourceNodeType)
|
|
408
|
+
: { enabled: false, triggerPort: 'execute' };
|
|
409
|
+
const isPullNode = pullConfig.enabled;
|
|
410
|
+
const needsUndefinedCheck = !isStartNode(sourceNode) &&
|
|
411
|
+
(nodesInBranches.has(sourceNode) ||
|
|
412
|
+
branchingNodes.has(sourceNode) ||
|
|
413
|
+
pullExecutionNodes.has(sourceNode) ||
|
|
414
|
+
nodeLevelScopedChildren.has(sourceNode));
|
|
415
|
+
if (isPullNode) {
|
|
416
|
+
return `${awaitKeyword}ctx.getVariable({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx}!, nodeTypeName: '${sourceNode}' })`;
|
|
417
|
+
}
|
|
418
|
+
else if (needsUndefinedCheck) {
|
|
419
|
+
return `${sourceIdx} !== undefined ? ${awaitKeyword}ctx.getVariable({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} }) : ${defaultValue}`;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
return `${awaitKeyword}ctx.getVariable({ id: '${sourceNode}', portName: '${sourcePort}', executionIndex: ${sourceIdx} })`;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const isControlFlowPort = exitPort === 'onSuccess' || exitPort === 'onFailure';
|
|
426
|
+
const defaultValue = isControlFlowPort ? 'false' : 'undefined';
|
|
427
|
+
if (validConns.length === 1) {
|
|
428
|
+
// Single connection - straightforward assignment
|
|
429
|
+
lines.push(` const ${varName} = ${buildSourceExpr(validConns[0], defaultValue)};`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Multiple connections - coalesce with || (STEP ports) or ?? (data ports)
|
|
433
|
+
const operator = isControlFlowPort ? ' || ' : ' ?? ';
|
|
434
|
+
const parts = validConns.map((conn) => `(${buildSourceExpr(conn, defaultValue)})`);
|
|
435
|
+
lines.push(` const ${varName} = ${parts.join(operator)};`);
|
|
436
|
+
}
|
|
437
|
+
// Emit VARIABLE_SET for Exit node INPUT ports
|
|
438
|
+
if (!production) {
|
|
439
|
+
lines.push(` ${setCall}({ id: '${RESERVED_NODE_NAMES.EXIT}', portName: '${exitPort}', executionIndex: exitIdx, nodeTypeName: '${RESERVED_NODE_NAMES.EXIT}' }, ${varName});`);
|
|
440
|
+
}
|
|
441
|
+
// Cast to the exit port's declared type for type safety
|
|
442
|
+
returnProps.push(`${exitPort}: ${varName} as ${exitPortType}`);
|
|
443
|
+
});
|
|
444
|
+
// Add default undefined for declared exit ports that weren't connected
|
|
445
|
+
// (e.g., when connection had typo like Exit.resultx instead of Exit.result)
|
|
446
|
+
const connectedExitPorts = new Set(returnProps.map((prop) => prop.split(':')[0]));
|
|
447
|
+
Object.entries(workflow.exitPorts).forEach(([portName, portDef]) => {
|
|
448
|
+
if (!connectedExitPorts.has(portName) && portName !== 'onSuccess' && portName !== 'onFailure') {
|
|
449
|
+
const portType = portDef?.tsType || (portDef ? mapToTypeScript(portDef.dataType) : 'unknown');
|
|
450
|
+
lines.push(` // Exit port '${portName}' has no valid connection - using undefined`);
|
|
451
|
+
returnProps.push(`${portName}: undefined as unknown as ${portType}`);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
lines.push('');
|
|
455
|
+
// Check if onSuccess/onFailure are explicitly connected
|
|
456
|
+
const hasOnSuccess = returnProps.some((prop) => prop.startsWith('onSuccess:'));
|
|
457
|
+
const hasOnFailure = returnProps.some((prop) => prop.startsWith('onFailure:'));
|
|
458
|
+
// Only add defaults if not explicitly connected
|
|
459
|
+
const defaults = [];
|
|
460
|
+
const setCallForDefaults = isAsync ? 'await ctx.setVariable' : 'ctx.setVariable';
|
|
461
|
+
if (!hasOnSuccess) {
|
|
462
|
+
defaults.push('onSuccess: true');
|
|
463
|
+
// Emit VARIABLE_SET for default onSuccess
|
|
464
|
+
if (!production) {
|
|
465
|
+
lines.push(` ${setCallForDefaults}({ id: '${RESERVED_NODE_NAMES.EXIT}', portName: 'onSuccess', executionIndex: exitIdx, nodeTypeName: '${RESERVED_NODE_NAMES.EXIT}' }, true);`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!hasOnFailure) {
|
|
469
|
+
defaults.push('onFailure: false');
|
|
470
|
+
// Emit VARIABLE_SET for default onFailure
|
|
471
|
+
if (!production) {
|
|
472
|
+
lines.push(` ${setCallForDefaults}({ id: '${RESERVED_NODE_NAMES.EXIT}', portName: 'onFailure', executionIndex: exitIdx, nodeTypeName: '${RESERVED_NODE_NAMES.EXIT}' }, false);`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const allProps = defaults.length > 0
|
|
476
|
+
? `${defaults.join(', ')}${returnProps.length > 0 ? ', ' : ''}${returnProps.join(', ')}`
|
|
477
|
+
: returnProps.join(', ');
|
|
478
|
+
lines.push(` const finalResult = { ${allProps} };`);
|
|
479
|
+
lines.push('');
|
|
480
|
+
lines.push(` ctx.sendStatusChangedEvent({`);
|
|
481
|
+
lines.push(` nodeTypeName: '${RESERVED_NODE_NAMES.EXIT}',`);
|
|
482
|
+
lines.push(` id: '${RESERVED_NODE_NAMES.EXIT}',`);
|
|
483
|
+
lines.push(` executionIndex: exitIdx,`);
|
|
484
|
+
lines.push(` status: 'SUCCEEDED',`);
|
|
485
|
+
lines.push(` });`);
|
|
486
|
+
lines.push(` ctx.sendWorkflowCompletedEvent({`);
|
|
487
|
+
lines.push(` executionIndex: exitIdx,`);
|
|
488
|
+
lines.push(` status: 'SUCCEEDED',`);
|
|
489
|
+
lines.push(` result: finalResult,`);
|
|
490
|
+
lines.push(` });`);
|
|
491
|
+
lines.push('');
|
|
492
|
+
lines.push(` return finalResult;`);
|
|
493
|
+
return lines.join('\n');
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Helper function to generate scoped children execution for nodes that create scopes
|
|
497
|
+
*/
|
|
498
|
+
function generateScopedChildrenExecution(parentInstance, parentNodeType, workflow, allNodeTypes, generatedNodes, availableVars, lines, indent, branchingNodes, branchRegions, isAsync, bundleMode = false) {
|
|
499
|
+
// Check if this node creates a scope
|
|
500
|
+
if (!parentNodeType.scope)
|
|
501
|
+
return;
|
|
502
|
+
// Check if this is a per-port scope (has scoped OUTPUT ports)
|
|
503
|
+
// Per-port scopes have children handled inside the scope function closure generated by
|
|
504
|
+
// buildNodeArgumentsWithContext -> generateScopeFunctionClosure, so we skip here
|
|
505
|
+
const rawScopeName = parentNodeType.scope;
|
|
506
|
+
const hasPerPortScope = Object.values(parentNodeType.outputs).some((portDef) => portDef.scope === rawScopeName);
|
|
507
|
+
if (hasPerPortScope) {
|
|
508
|
+
// Children are already generated inside the scope function closure
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const scopeName = `${parentInstance.id}.${parentNodeType.scope}`;
|
|
512
|
+
// Find children in this scope from workflow.scopes or instance.parentScope
|
|
513
|
+
const childrenInScope = [];
|
|
514
|
+
// Check workflow.scopes mapping
|
|
515
|
+
if (workflow.scopes && workflow.scopes[scopeName]) {
|
|
516
|
+
childrenInScope.push(...workflow.scopes[scopeName]);
|
|
517
|
+
}
|
|
518
|
+
// Also check instances with parent field
|
|
519
|
+
workflow.instances.forEach((instance) => {
|
|
520
|
+
const parentScopeName = instance.parent
|
|
521
|
+
? `${instance.parent.id}.${instance.parent.scope}`
|
|
522
|
+
: null;
|
|
523
|
+
if (parentScopeName === scopeName && !childrenInScope.includes(instance.id)) {
|
|
524
|
+
childrenInScope.push(instance.id);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
if (childrenInScope.length === 0)
|
|
528
|
+
return;
|
|
529
|
+
// Generate scope creation
|
|
530
|
+
const safeParentId = toValidIdentifier(parentInstance.id);
|
|
531
|
+
lines.push(``);
|
|
532
|
+
lines.push(`${indent}// Create scope for children of ${parentInstance.id}`);
|
|
533
|
+
lines.push(`${indent}const ${safeParentId}_scopedCtx = ctx.createScope('${parentInstance.id}', ${safeParentId}Idx, '${parentNodeType.scope}');`);
|
|
534
|
+
lines.push(``);
|
|
535
|
+
// Scoped context variable name for child generators
|
|
536
|
+
const scopedCtxVar = `${safeParentId}_scopedCtx`;
|
|
537
|
+
// Generate child nodes execution using scoped context
|
|
538
|
+
childrenInScope.forEach((childInstanceId) => {
|
|
539
|
+
if (generatedNodes.has(childInstanceId))
|
|
540
|
+
return;
|
|
541
|
+
const childInstance = workflow.instances.find((i) => i.id === childInstanceId);
|
|
542
|
+
if (!childInstance)
|
|
543
|
+
return;
|
|
544
|
+
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
545
|
+
const childNodeType = allNodeTypes.find((nt) => nt.name === childInstance.nodeType || nt.functionName === childInstance.nodeType);
|
|
546
|
+
if (!childNodeType)
|
|
547
|
+
return;
|
|
548
|
+
// Check if this child is a branching node
|
|
549
|
+
if (branchingNodes.has(childInstanceId)) {
|
|
550
|
+
generateBranchingNodeCode(childInstance, childNodeType, workflow, allNodeTypes, branchRegions.get(childInstanceId), availableVars, generatedNodes, lines, indent, false, branchingNodes, branchRegions, isAsync, scopedCtxVar, // Pass scoped context name
|
|
551
|
+
bundleMode);
|
|
552
|
+
const region = branchRegions.get(childInstanceId);
|
|
553
|
+
region.successNodes.forEach((n) => generatedNodes.add(n));
|
|
554
|
+
region.failureNodes.forEach((n) => generatedNodes.add(n));
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
generateNodeCallWithContext(childInstance, childNodeType, workflow, availableVars, lines, allNodeTypes, indent, isAsync, false, // useConst = false - scoped children need let (referenced outside scope block)
|
|
558
|
+
parentInstance.id, // instanceParent - parent node is const, no ! needed when referencing it
|
|
559
|
+
scopedCtxVar, // Pass scoped context name
|
|
560
|
+
bundleMode);
|
|
561
|
+
}
|
|
562
|
+
generatedNodes.add(childInstanceId);
|
|
563
|
+
});
|
|
564
|
+
lines.push(``);
|
|
565
|
+
lines.push(`${indent}// Merge scope back into parent context`);
|
|
566
|
+
lines.push(`${indent}ctx.mergeScope(${parentInstance.id}_scopedCtx);`);
|
|
567
|
+
lines.push(``);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Sort branch nodes topologically based on their dependencies
|
|
571
|
+
*
|
|
572
|
+
* This ensures nodes within a branch execute in the correct order:
|
|
573
|
+
* dependencies before dependents.
|
|
574
|
+
*
|
|
575
|
+
* @param nodeIds - Set of node IDs within the branch
|
|
576
|
+
* @param workflow - The workflow AST
|
|
577
|
+
* @returns Array of node IDs in topologically sorted order
|
|
578
|
+
*/
|
|
579
|
+
function sortBranchNodesTopologically(nodeIds, workflow) {
|
|
580
|
+
if (nodeIds.size === 0) {
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
// Build a mini control flow graph for just these nodes
|
|
584
|
+
const graph = new Map();
|
|
585
|
+
const inDegree = new Map();
|
|
586
|
+
// Initialize
|
|
587
|
+
nodeIds.forEach((id) => {
|
|
588
|
+
graph.set(id, []);
|
|
589
|
+
inDegree.set(id, 0);
|
|
590
|
+
});
|
|
591
|
+
// Add edges based on data flow connections within the branch
|
|
592
|
+
workflow.connections.forEach((conn) => {
|
|
593
|
+
const fromNode = conn.from.node;
|
|
594
|
+
const toNode = conn.to.node;
|
|
595
|
+
// Only consider connections within this branch
|
|
596
|
+
if (nodeIds.has(fromNode) && nodeIds.has(toNode)) {
|
|
597
|
+
const successors = graph.get(fromNode) || [];
|
|
598
|
+
if (!successors.includes(toNode)) {
|
|
599
|
+
successors.push(toNode);
|
|
600
|
+
graph.set(fromNode, successors);
|
|
601
|
+
inDegree.set(toNode, (inDegree.get(toNode) || 0) + 1);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
// Perform Kahn's topological sort
|
|
606
|
+
const result = [];
|
|
607
|
+
const queue = [];
|
|
608
|
+
// Start with nodes that have no dependencies within the branch
|
|
609
|
+
inDegree.forEach((degree, node) => {
|
|
610
|
+
if (degree === 0) {
|
|
611
|
+
queue.push(node);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
while (queue.length > 0) {
|
|
615
|
+
const node = queue.shift();
|
|
616
|
+
result.push(node);
|
|
617
|
+
const successors = graph.get(node) || [];
|
|
618
|
+
successors.forEach((successor) => {
|
|
619
|
+
const newDegree = (inDegree.get(successor) || 0) - 1;
|
|
620
|
+
inDegree.set(successor, newDegree);
|
|
621
|
+
if (newDegree === 0) {
|
|
622
|
+
queue.push(successor);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
// If we haven't processed all nodes, there's a cycle (shouldn't happen after validation)
|
|
627
|
+
if (result.length !== nodeIds.size) {
|
|
628
|
+
// Fallback: add remaining nodes in arbitrary order
|
|
629
|
+
nodeIds.forEach((id) => {
|
|
630
|
+
if (!result.includes(id)) {
|
|
631
|
+
result.push(id);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Generates CANCELLED status events for all nodes in a branch that won't be executed.
|
|
639
|
+
* This is called when a branching node decides to take one path, marking nodes in
|
|
640
|
+
* the non-taken path as CANCELLED. We must add an execution index first to have a
|
|
641
|
+
* valid reference for the event.
|
|
642
|
+
*/
|
|
643
|
+
function generateCancelledEventsForBranch(nodeIds, workflow, allNodeTypes, lines, indent, ctxVar = 'ctx' // Context variable name (for scoped contexts)
|
|
644
|
+
) {
|
|
645
|
+
nodeIds.forEach((instanceId) => {
|
|
646
|
+
const instance = workflow.instances.find((i) => i.id === instanceId);
|
|
647
|
+
if (!instance)
|
|
648
|
+
return;
|
|
649
|
+
const safeId = toValidIdentifier(instanceId);
|
|
650
|
+
// Add execution index for this skipped node so the event has a valid reference
|
|
651
|
+
lines.push(`${indent}const ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
652
|
+
lines.push(`${indent}${ctxVar}.sendStatusChangedEvent({`);
|
|
653
|
+
lines.push(`${indent} nodeTypeName: '${instance.nodeType}',`);
|
|
654
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
655
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
656
|
+
lines.push(`${indent} status: 'CANCELLED',`);
|
|
657
|
+
lines.push(`${indent}});`);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Build a guard condition for a STEP connection source.
|
|
662
|
+
* For branching nodes whose onSuccess/onFailure port is the source,
|
|
663
|
+
* use the _success flag to guard the correct branch path.
|
|
664
|
+
* For other sources, use Idx !== undefined.
|
|
665
|
+
*/
|
|
666
|
+
function buildStepSourceCondition(sourceNode, sourcePort, branchingNodes) {
|
|
667
|
+
const safeNode = toValidIdentifier(sourceNode);
|
|
668
|
+
if (branchingNodes.has(sourceNode)) {
|
|
669
|
+
if (isSuccessPort(sourcePort)) {
|
|
670
|
+
return `${safeNode}_success`;
|
|
671
|
+
}
|
|
672
|
+
if (isFailurePort(sourcePort)) {
|
|
673
|
+
return `${safeNode}_success === false`;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return `${safeNode}Idx !== undefined`;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Generate flat code for a sequential chain of branching nodes.
|
|
680
|
+
*
|
|
681
|
+
* Instead of nesting each subsequent branching node inside the previous one's
|
|
682
|
+
* success/failure branch (O(N) depth), this generates them sequentially with
|
|
683
|
+
* accumulated guard conditions (O(1) depth).
|
|
684
|
+
*
|
|
685
|
+
* For chain [A, B, C]:
|
|
686
|
+
* A code (no guard)
|
|
687
|
+
* if (A_success) { B code } else { CANCELLED for B,C and regions }
|
|
688
|
+
* if (A_success && B_success) { C code } else { CANCELLED for C and regions }
|
|
689
|
+
*/
|
|
690
|
+
function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes, branchRegions, availableVars, generatedNodes, lines, indent, isAsync, ctxVar, bundleMode, forceTrackSuccessNodes = new Set()) {
|
|
691
|
+
// Pre-declare success flags for all non-last chain nodes so they're
|
|
692
|
+
// accessible across guard blocks (avoiding let-in-block scoping issues).
|
|
693
|
+
// Also pre-declare for the last node if promoted nodes depend on its _success flag.
|
|
694
|
+
const preDeclaredFlags = new Set();
|
|
695
|
+
for (let i = 0; i < chain.length; i++) {
|
|
696
|
+
const isLast = i === chain.length - 1;
|
|
697
|
+
if (!isLast || forceTrackSuccessNodes.has(chain[i])) {
|
|
698
|
+
const safeId = toValidIdentifier(chain[i]);
|
|
699
|
+
lines.push(`${indent}let ${safeId}_success = false;`);
|
|
700
|
+
preDeclaredFlags.add(safeId);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (chain.length > 1) {
|
|
704
|
+
lines.push('');
|
|
705
|
+
}
|
|
706
|
+
const guardParts = [];
|
|
707
|
+
for (let i = 0; i < chain.length; i++) {
|
|
708
|
+
const nodeId = chain[i];
|
|
709
|
+
const isLast = i === chain.length - 1;
|
|
710
|
+
const instance = workflow.instances.find((inst) => inst.id === nodeId);
|
|
711
|
+
if (!instance)
|
|
712
|
+
continue;
|
|
713
|
+
const nodeType = nodeTypes.find((nt) => nt.name === instance.nodeType || nt.functionName === instance.nodeType);
|
|
714
|
+
if (!nodeType)
|
|
715
|
+
continue;
|
|
716
|
+
const safeId = toValidIdentifier(nodeId);
|
|
717
|
+
const originalRegion = branchRegions.get(nodeId);
|
|
718
|
+
// For non-last nodes, create modified region with chain successor removed
|
|
719
|
+
// so generateBranchingNodeCode won't recurse into the next chain node
|
|
720
|
+
let effectiveRegion = originalRegion;
|
|
721
|
+
if (!isLast) {
|
|
722
|
+
const nextNodeId = chain[i + 1];
|
|
723
|
+
effectiveRegion = {
|
|
724
|
+
successNodes: new Set([...originalRegion.successNodes].filter((n) => n !== nextNodeId)),
|
|
725
|
+
failureNodes: new Set([...originalRegion.failureNodes].filter((n) => n !== nextNodeId)),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const hasGuard = guardParts.length > 0;
|
|
729
|
+
const guardCondition = guardParts.join(' && ');
|
|
730
|
+
if (hasGuard) {
|
|
731
|
+
lines.push(`${indent}if (${guardCondition}) {`);
|
|
732
|
+
}
|
|
733
|
+
const nodeIndent = hasGuard ? indent + ' ' : indent;
|
|
734
|
+
generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]) // forceTrackSuccess for non-last chain nodes or nodes with promoted dependents
|
|
735
|
+
);
|
|
736
|
+
// Generate scoped children for this chain node
|
|
737
|
+
generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, nodeIndent, branchingNodes, branchRegions, isAsync, bundleMode);
|
|
738
|
+
if (hasGuard) {
|
|
739
|
+
lines.push(`${indent}} else {`);
|
|
740
|
+
// Emit CANCELLED for this node and all remaining chain nodes + their regions
|
|
741
|
+
const remainingNodes = new Set();
|
|
742
|
+
for (let j = i; j < chain.length; j++) {
|
|
743
|
+
const chainNodeId = chain[j];
|
|
744
|
+
remainingNodes.add(chainNodeId);
|
|
745
|
+
const region = branchRegions.get(chainNodeId);
|
|
746
|
+
region.successNodes.forEach((n) => remainingNodes.add(n));
|
|
747
|
+
region.failureNodes.forEach((n) => remainingNodes.add(n));
|
|
748
|
+
}
|
|
749
|
+
generateCancelledEventsForBranch(remainingNodes, workflow, nodeTypes, lines, indent + ' ', ctxVar);
|
|
750
|
+
lines.push(`${indent}}`);
|
|
751
|
+
}
|
|
752
|
+
// Add success condition for next iteration's guard
|
|
753
|
+
if (!isLast) {
|
|
754
|
+
guardParts.push(`${safeId}_success`);
|
|
755
|
+
}
|
|
756
|
+
// Mark node and its effective region as generated
|
|
757
|
+
generatedNodes.add(nodeId);
|
|
758
|
+
effectiveRegion.successNodes.forEach((n) => generatedNodes.add(n));
|
|
759
|
+
effectiveRegion.failureNodes.forEach((n) => generatedNodes.add(n));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function generateBranchingNodeCode(instance, branchNode, workflow, allNodeTypes, region, availableVars, generatedNodes, lines, indent, _generateReturns = true, // DEPRECATED: always false, kept for signature compat
|
|
763
|
+
branchingNodes, branchRegions, isAsync, ctxVar = 'ctx', // Context variable name (for scoped contexts)
|
|
764
|
+
bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = false) {
|
|
765
|
+
const instanceId = instance.id;
|
|
766
|
+
const safeId = toValidIdentifier(instanceId);
|
|
767
|
+
const functionName = branchNode.functionName;
|
|
768
|
+
lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
|
|
769
|
+
lines.push(`${indent}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
770
|
+
lines.push(`${indent}${ctxVar}.sendStatusChangedEvent({`);
|
|
771
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
772
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
773
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
774
|
+
lines.push(`${indent} status: 'RUNNING',`);
|
|
775
|
+
lines.push(`${indent}});`);
|
|
776
|
+
lines.push('');
|
|
777
|
+
// Only declare success flag if there are downstream nodes
|
|
778
|
+
const hasSuccessDownstream = region.successNodes.size > 0;
|
|
779
|
+
const hasFailureDownstream = region.failureNodes.size > 0;
|
|
780
|
+
const hasDownstream = hasSuccessDownstream || hasFailureDownstream;
|
|
781
|
+
// Track success flag when there are downstream nodes OR when chain code needs it
|
|
782
|
+
const trackSuccess = hasDownstream || forceTrackSuccess;
|
|
783
|
+
if (trackSuccess) {
|
|
784
|
+
if (preDeclaredSuccessFlags.has(safeId)) {
|
|
785
|
+
// Flag was pre-declared by chain code — use assignment, not declaration
|
|
786
|
+
lines.push(`${indent}${safeId}_success = false;`);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
lines.push(`${indent}let ${safeId}_success = false;`);
|
|
790
|
+
}
|
|
791
|
+
lines.push('');
|
|
792
|
+
}
|
|
793
|
+
lines.push(`${indent}try {`);
|
|
794
|
+
const getCall = isAsync ? `await ${ctxVar}.getVariable` : `${ctxVar}.getVariable`;
|
|
795
|
+
const setCall = isAsync ? `await ${ctxVar}.setVariable` : `${ctxVar}.setVariable`;
|
|
796
|
+
const argNames = buildNodeArgumentsWithContext({
|
|
797
|
+
node: branchNode,
|
|
798
|
+
workflow,
|
|
799
|
+
id: instanceId,
|
|
800
|
+
lines,
|
|
801
|
+
indent: `${indent} `,
|
|
802
|
+
getCall,
|
|
803
|
+
isAsync,
|
|
804
|
+
emitInputEvents: true,
|
|
805
|
+
setCall,
|
|
806
|
+
nodeTypeName: functionName,
|
|
807
|
+
bundleMode,
|
|
808
|
+
});
|
|
809
|
+
const awaitKeyword = branchNode.isAsync ? 'await ' : '';
|
|
810
|
+
if (branchNode.expression) {
|
|
811
|
+
// Expression branching node: call without execute, auto-set onSuccess/onFailure
|
|
812
|
+
lines.push(`${indent} const ${safeId}Result = ${awaitKeyword}${functionName}(${argNames.join(', ')});`);
|
|
813
|
+
// Determine data output ports (exclude control flow and scoped ports)
|
|
814
|
+
const dataOutputPorts = Object.keys(branchNode.outputs).filter((portName) => {
|
|
815
|
+
const portConfig = branchNode.outputs[portName];
|
|
816
|
+
if (portConfig.scope)
|
|
817
|
+
return false;
|
|
818
|
+
if (isSuccessPort(portName) || isFailurePort(portName))
|
|
819
|
+
return false;
|
|
820
|
+
if (portConfig.isControlFlow || portConfig.failure)
|
|
821
|
+
return false;
|
|
822
|
+
return true;
|
|
823
|
+
});
|
|
824
|
+
if (dataOutputPorts.length === 1) {
|
|
825
|
+
// Single data output: destructure if result is an object with the port key, else use raw value
|
|
826
|
+
// Extract to unknown-typed variable to prevent TypeScript from narrowing
|
|
827
|
+
// specific return types (e.g. boolean) to `never` in the typeof check
|
|
828
|
+
const portName = dataOutputPorts[0];
|
|
829
|
+
const rawVar = `${safeId}Result_raw`;
|
|
830
|
+
lines.push(`${indent} const ${rawVar}: unknown = ${safeId}Result;`);
|
|
831
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, typeof ${rawVar} === 'object' && ${rawVar} !== null && '${portName}' in ${rawVar} ? ${rawVar}.${portName} : ${rawVar});`);
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// Multiple data outputs: destructure from object return
|
|
835
|
+
dataOutputPorts.forEach((portName) => {
|
|
836
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${safeId}Result.${portName});`);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
// Auto-set onSuccess/onFailure
|
|
840
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`);
|
|
841
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`);
|
|
842
|
+
}
|
|
843
|
+
else if (branchNode.variant === 'MAP_ITERATOR') {
|
|
844
|
+
// MAP_ITERATOR: inline iteration — no user function to call
|
|
845
|
+
// argNames: [execute, items, scopeFn]
|
|
846
|
+
const executeArg = argNames[0];
|
|
847
|
+
const itemsArg = argNames[1];
|
|
848
|
+
const scopeFnArg = argNames[2];
|
|
849
|
+
lines.push(`${indent} let ${safeId}Result: { onSuccess: boolean; onFailure: boolean; results: unknown[] };`);
|
|
850
|
+
lines.push(`${indent} if (!${executeArg}) {`);
|
|
851
|
+
lines.push(`${indent} ${safeId}Result = { onSuccess: false, onFailure: false, results: [] };`);
|
|
852
|
+
lines.push(`${indent} } else {`);
|
|
853
|
+
lines.push(`${indent} const __results: unknown[] = [];`);
|
|
854
|
+
lines.push(`${indent} for (const __item of ${itemsArg}) {`);
|
|
855
|
+
lines.push(`${indent} __results.push((${isAsync ? 'await ' : ''}${scopeFnArg}(true, __item)).processed);`);
|
|
856
|
+
lines.push(`${indent} }`);
|
|
857
|
+
lines.push(`${indent} ${safeId}Result = { onSuccess: true, onFailure: false, results: __results };`);
|
|
858
|
+
lines.push(`${indent} }`);
|
|
859
|
+
// Set output ports from result
|
|
860
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
861
|
+
const portConfig = branchNode.outputs[portName];
|
|
862
|
+
if (portConfig.scope)
|
|
863
|
+
return;
|
|
864
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${safeId}Result.${portName});`);
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
else if (branchNode.variant === 'IMPORTED_WORKFLOW' || branchNode.variant === 'WORKFLOW') {
|
|
868
|
+
// Check if this is a workflow call (IMPORTED_WORKFLOW or WORKFLOW variant)
|
|
869
|
+
// Workflows use (execute, params) signature where params is an object
|
|
870
|
+
// Regular nodes use (execute, arg1, arg2, ...) positional signature
|
|
871
|
+
// For workflow calls, wrap data args in an object and include recursion depth
|
|
872
|
+
const executeArg = argNames[0]; // First arg is always execute
|
|
873
|
+
const dataArgs = argNames.slice(1); // Rest are data inputs
|
|
874
|
+
const inputPortNames = Object.keys(branchNode.inputs).filter((p) => !isExecutePort(p));
|
|
875
|
+
// Build params object: { portName1: value1, portName2: value2, ..., __rd__: __rd__ + 1 }
|
|
876
|
+
// Assign to variable first to avoid TypeScript excess property checking on object literals
|
|
877
|
+
const paramsEntries = inputPortNames.map((portName, i) => `${portName}: ${dataArgs[i]}`);
|
|
878
|
+
paramsEntries.push('__rd__: __rd__ + 1');
|
|
879
|
+
const paramsObj = `{ ${paramsEntries.join(', ')} }`;
|
|
880
|
+
const paramsVar = `__${safeId}Params__`;
|
|
881
|
+
lines.push(`${indent} const ${paramsVar} = ${paramsObj};`);
|
|
882
|
+
lines.push(`${indent} const ${safeId}Result = ${awaitKeyword}${functionName}(${executeArg}, ${paramsVar});`);
|
|
883
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
884
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
885
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
886
|
+
const portConfig = branchNode.outputs[portName];
|
|
887
|
+
if (portConfig.scope)
|
|
888
|
+
return;
|
|
889
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${safeId}Result.${portName});`);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
else if (branchNode.scope || (branchNode.scopes && branchNode.scopes.length > 0)) {
|
|
893
|
+
// Scoped node call with positional arguments (uses _impl signature)
|
|
894
|
+
// Scoped nodes have callback parameters that can't be passed via params object
|
|
895
|
+
lines.push(`${indent} const ${safeId}Result = ${awaitKeyword}${functionName}(${argNames.join(', ')});`);
|
|
896
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
897
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
898
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
899
|
+
const portConfig = branchNode.outputs[portName];
|
|
900
|
+
if (portConfig.scope)
|
|
901
|
+
return;
|
|
902
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${safeId}Result.${portName});`);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
// Regular node call - always use positional args
|
|
907
|
+
// In bundle mode we import _impl which takes (execute, ...positional_args)
|
|
908
|
+
lines.push(`${indent} const ${safeId}Result = ${awaitKeyword}${functionName}(${argNames.join(', ')});`);
|
|
909
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
910
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
911
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
912
|
+
const portConfig = branchNode.outputs[portName];
|
|
913
|
+
if (portConfig.scope)
|
|
914
|
+
return;
|
|
915
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${safeId}Result.${portName});`);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
919
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
920
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
921
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
922
|
+
lines.push(`${indent} status: 'SUCCEEDED',`);
|
|
923
|
+
lines.push(`${indent} });`);
|
|
924
|
+
// Use onSuccess from result to determine control flow
|
|
925
|
+
// For expression nodes, onSuccess is always true here (catch handles failure)
|
|
926
|
+
if (trackSuccess) {
|
|
927
|
+
lines.push(`${indent} ${safeId}_success = ${branchNode.expression ? 'true' : `${safeId}Result.onSuccess`};`);
|
|
928
|
+
}
|
|
929
|
+
lines.push(`${indent}} catch (error: unknown) {`);
|
|
930
|
+
lines.push(`${indent} const isCancellation = CancellationError.isCancellationError(error);`);
|
|
931
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
932
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
933
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
934
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
935
|
+
lines.push(`${indent} status: isCancellation ? 'CANCELLED' : 'FAILED',`);
|
|
936
|
+
lines.push(`${indent} });`);
|
|
937
|
+
lines.push(`${indent} if (!isCancellation) {`);
|
|
938
|
+
lines.push(`${indent} ${ctxVar}.sendLogErrorEvent({`);
|
|
939
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
940
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
941
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
942
|
+
lines.push(`${indent} error: error instanceof Error ? error.message : String(error),`);
|
|
943
|
+
lines.push(`${indent} });`);
|
|
944
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`);
|
|
945
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`);
|
|
946
|
+
if (trackSuccess) {
|
|
947
|
+
lines.push(`${indent} ${safeId}_success = false;`);
|
|
948
|
+
}
|
|
949
|
+
lines.push(`${indent} }`);
|
|
950
|
+
// Emit CANCELLED for all downstream nodes since branching node threw
|
|
951
|
+
if (hasSuccessDownstream) {
|
|
952
|
+
generateCancelledEventsForBranch(region.successNodes, workflow, allNodeTypes, lines, `${indent} `, ctxVar);
|
|
953
|
+
}
|
|
954
|
+
if (hasFailureDownstream) {
|
|
955
|
+
generateCancelledEventsForBranch(region.failureNodes, workflow, allNodeTypes, lines, `${indent} `, ctxVar);
|
|
956
|
+
}
|
|
957
|
+
// Re-throw the error to propagate it up (important for recursive workflows)
|
|
958
|
+
lines.push(`${indent} throw error;`);
|
|
959
|
+
lines.push(`${indent}}`);
|
|
960
|
+
lines.push('');
|
|
961
|
+
// Only generate if/else if there are downstream nodes
|
|
962
|
+
if (hasDownstream) {
|
|
963
|
+
lines.push(`${indent}if (${safeId}_success) {`);
|
|
964
|
+
// Emit CANCELLED for failure branch nodes since success path was taken
|
|
965
|
+
if (hasFailureDownstream) {
|
|
966
|
+
generateCancelledEventsForBranch(region.failureNodes, workflow, allNodeTypes, lines, `${indent} `, ctxVar);
|
|
967
|
+
}
|
|
968
|
+
const successVars = new Map(availableVars);
|
|
969
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
970
|
+
successVars.set(`${instanceId}.${portName}`, `${safeId}Result.${portName}`);
|
|
971
|
+
});
|
|
972
|
+
// Sort success branch nodes topologically to ensure correct execution order
|
|
973
|
+
const successInstanceIds = sortBranchNodesTopologically(region.successNodes, workflow);
|
|
974
|
+
const successExecutedNodes = [instance.id];
|
|
975
|
+
successInstanceIds.forEach((instanceId) => {
|
|
976
|
+
const inst = workflow.instances.find((i) => i.id === instanceId);
|
|
977
|
+
if (!inst)
|
|
978
|
+
return;
|
|
979
|
+
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
980
|
+
const nodeType = allNodeTypes.find((nt) => nt.name === inst.nodeType || nt.functionName === inst.nodeType);
|
|
981
|
+
if (!nodeType)
|
|
982
|
+
return;
|
|
983
|
+
if (branchingNodes.has(instanceId)) {
|
|
984
|
+
const nestedRegion = branchRegions.get(instanceId);
|
|
985
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode);
|
|
986
|
+
successExecutedNodes.push(instanceId);
|
|
987
|
+
nestedRegion.successNodes.forEach((n) => successExecutedNodes.push(n));
|
|
988
|
+
nestedRegion.failureNodes.forEach((n) => successExecutedNodes.push(n));
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
generateNodeCallWithContext(inst, nodeType, workflow, successVars, lines, allNodeTypes, `${indent} `, isAsync, false, // useConst
|
|
992
|
+
undefined, // instanceParent
|
|
993
|
+
ctxVar, bundleMode, true // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
994
|
+
);
|
|
995
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
996
|
+
successVars.set(`${instanceId}.${portName}`, `${toValidIdentifier(instanceId)}Result.${portName}`);
|
|
997
|
+
});
|
|
998
|
+
successExecutedNodes.push(instanceId);
|
|
999
|
+
generatedNodes.add(instanceId);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
// Only generate else block if there are failure nodes to execute
|
|
1003
|
+
if (hasFailureDownstream) {
|
|
1004
|
+
lines.push(`${indent}} else {`);
|
|
1005
|
+
// Emit CANCELLED for success branch nodes since failure path was taken
|
|
1006
|
+
if (hasSuccessDownstream) {
|
|
1007
|
+
generateCancelledEventsForBranch(region.successNodes, workflow, allNodeTypes, lines, `${indent} `, ctxVar);
|
|
1008
|
+
}
|
|
1009
|
+
const failureVars = new Map(availableVars);
|
|
1010
|
+
Object.keys(branchNode.outputs).forEach((portName) => {
|
|
1011
|
+
failureVars.set(`${instanceId}.${portName}`, `${safeId}Result.${portName}`);
|
|
1012
|
+
});
|
|
1013
|
+
// Sort failure branch nodes topologically to ensure correct execution order
|
|
1014
|
+
const failureInstanceIds = sortBranchNodesTopologically(region.failureNodes, workflow);
|
|
1015
|
+
const failureExecutedNodes = [instance.id];
|
|
1016
|
+
failureInstanceIds.forEach((instanceId) => {
|
|
1017
|
+
const inst = workflow.instances.find((i) => i.id === instanceId);
|
|
1018
|
+
if (!inst)
|
|
1019
|
+
return;
|
|
1020
|
+
// Check both name (for npm nodes like 'npm/pkg/func') and functionName (for local nodes)
|
|
1021
|
+
const nodeType = allNodeTypes.find((nt) => nt.name === inst.nodeType || nt.functionName === inst.nodeType);
|
|
1022
|
+
if (!nodeType)
|
|
1023
|
+
return;
|
|
1024
|
+
if (branchingNodes.has(instanceId)) {
|
|
1025
|
+
const nestedRegion = branchRegions.get(instanceId);
|
|
1026
|
+
generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode);
|
|
1027
|
+
failureExecutedNodes.push(instanceId);
|
|
1028
|
+
nestedRegion.successNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
1029
|
+
nestedRegion.failureNodes.forEach((n) => failureExecutedNodes.push(n));
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
generateNodeCallWithContext(inst, nodeType, workflow, failureVars, lines, allNodeTypes, `${indent} `, isAsync, false, // useConst
|
|
1033
|
+
undefined, // instanceParent
|
|
1034
|
+
ctxVar, bundleMode, true // skipExecuteGuard — inside branch, execute is guaranteed by if/else
|
|
1035
|
+
);
|
|
1036
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1037
|
+
failureVars.set(`${instanceId}.${portName}`, `${toValidIdentifier(instanceId)}Result.${portName}`);
|
|
1038
|
+
});
|
|
1039
|
+
failureExecutedNodes.push(instanceId);
|
|
1040
|
+
generatedNodes.add(instanceId);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
lines.push(`${indent}}`);
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
// No failure branch - emit CANCELLED for success nodes and close
|
|
1047
|
+
if (hasSuccessDownstream) {
|
|
1048
|
+
lines.push(`${indent}} else {`);
|
|
1049
|
+
generateCancelledEventsForBranch(region.successNodes, workflow, allNodeTypes, lines, `${indent} `, ctxVar);
|
|
1050
|
+
lines.push(`${indent}}`);
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
lines.push(`${indent}}`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
generatedNodes.add(instanceId);
|
|
1058
|
+
}
|
|
1059
|
+
function generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync, ctxVar = 'ctx', // Context variable name (for scoped contexts)
|
|
1060
|
+
bundleMode = false) {
|
|
1061
|
+
const instanceId = instance.id;
|
|
1062
|
+
const safeId = toValidIdentifier(instanceId);
|
|
1063
|
+
const functionName = nodeType.functionName;
|
|
1064
|
+
// Executor must be async if:
|
|
1065
|
+
// 1. The workflow is async (context returns Promises)
|
|
1066
|
+
// 2. OR the node function is async (function returns Promise)
|
|
1067
|
+
// Sync executors only work when BOTH workflow and node are sync
|
|
1068
|
+
const executorIsAsync = isAsync || nodeType.isAsync;
|
|
1069
|
+
const asyncKeyword = executorIsAsync ? 'async ' : '';
|
|
1070
|
+
const awaitKeyword = nodeType.isAsync ? 'await ' : '';
|
|
1071
|
+
// Create a lazy execution function for this pull node
|
|
1072
|
+
// The function will only execute when its outputs are accessed
|
|
1073
|
+
lines.push(`${indent}// Pull execution node: ${instanceId}`);
|
|
1074
|
+
lines.push(`${indent}const ${safeId}_executor = ${asyncKeyword}() => {`);
|
|
1075
|
+
lines.push(`${indent} if (${safeId}Idx !== undefined) {`);
|
|
1076
|
+
lines.push(`${indent} return; // Already executed`);
|
|
1077
|
+
lines.push(`${indent} }`);
|
|
1078
|
+
lines.push(`${indent} ${ctxVar}.checkAborted('${instanceId}');`);
|
|
1079
|
+
lines.push(`${indent} ${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
1080
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
1081
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1082
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1083
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1084
|
+
lines.push(`${indent} status: 'RUNNING',`);
|
|
1085
|
+
lines.push(`${indent} });`);
|
|
1086
|
+
lines.push(`${indent} try {`);
|
|
1087
|
+
// Use executor's async status for get/set calls within the executor
|
|
1088
|
+
const getCall = executorIsAsync ? `await ${ctxVar}.getVariable` : `${ctxVar}.getVariable`;
|
|
1089
|
+
const setCall = executorIsAsync ? `await ${ctxVar}.setVariable` : `${ctxVar}.setVariable`;
|
|
1090
|
+
const args = buildNodeArgumentsWithContext({
|
|
1091
|
+
node: nodeType,
|
|
1092
|
+
workflow,
|
|
1093
|
+
id: instanceId,
|
|
1094
|
+
lines,
|
|
1095
|
+
indent: `${indent} `,
|
|
1096
|
+
getCall,
|
|
1097
|
+
isAsync: executorIsAsync,
|
|
1098
|
+
emitInputEvents: true,
|
|
1099
|
+
setCall,
|
|
1100
|
+
nodeTypeName: functionName,
|
|
1101
|
+
bundleMode,
|
|
1102
|
+
});
|
|
1103
|
+
const resultVar = `${safeId}Result`;
|
|
1104
|
+
// Check if this is a workflow call (IMPORTED_WORKFLOW or WORKFLOW variant)
|
|
1105
|
+
// Workflows use (execute, params) signature where params is an object
|
|
1106
|
+
// Regular nodes use (execute, arg1, arg2, ...) positional signature
|
|
1107
|
+
if (nodeType.variant === 'MAP_ITERATOR') {
|
|
1108
|
+
// MAP_ITERATOR: inline iteration in pull executor
|
|
1109
|
+
const executeArg = args[0];
|
|
1110
|
+
const itemsArg = args[1];
|
|
1111
|
+
const scopeFnArg = args[2];
|
|
1112
|
+
lines.push(`${indent} let ${resultVar}: { onSuccess: boolean; onFailure: boolean; results: unknown[] };`);
|
|
1113
|
+
lines.push(`${indent} if (!${executeArg}) {`);
|
|
1114
|
+
lines.push(`${indent} ${resultVar} = { onSuccess: false, onFailure: false, results: [] };`);
|
|
1115
|
+
lines.push(`${indent} } else {`);
|
|
1116
|
+
lines.push(`${indent} const __results: unknown[] = [];`);
|
|
1117
|
+
lines.push(`${indent} for (const __item of ${itemsArg}) {`);
|
|
1118
|
+
lines.push(`${indent} __results.push((${executorIsAsync ? 'await ' : ''}${scopeFnArg}(true, __item)).processed);`);
|
|
1119
|
+
lines.push(`${indent} }`);
|
|
1120
|
+
lines.push(`${indent} ${resultVar} = { onSuccess: true, onFailure: false, results: __results };`);
|
|
1121
|
+
lines.push(`${indent} }`);
|
|
1122
|
+
}
|
|
1123
|
+
else if (nodeType.variant === 'IMPORTED_WORKFLOW' || nodeType.variant === 'WORKFLOW') {
|
|
1124
|
+
// For workflow calls, wrap data args in an object and include recursion depth
|
|
1125
|
+
const executeArg = args[0]; // First arg is always execute
|
|
1126
|
+
const dataArgs = args.slice(1); // Rest are data inputs
|
|
1127
|
+
const inputPortNames = Object.keys(nodeType.inputs).filter((p) => !isExecutePort(p));
|
|
1128
|
+
// Build params object: { portName1: value1, portName2: value2, ..., __rd__: __rd__ + 1 }
|
|
1129
|
+
// Assign to variable first to avoid TypeScript excess property checking on object literals
|
|
1130
|
+
const paramsEntries = inputPortNames.map((portName, i) => `${portName}: ${dataArgs[i]}`);
|
|
1131
|
+
paramsEntries.push('__rd__: __rd__ + 1');
|
|
1132
|
+
const paramsObj = `{ ${paramsEntries.join(', ')} }`;
|
|
1133
|
+
const paramsVar = `__${safeId}Params__`;
|
|
1134
|
+
lines.push(`${indent} const ${paramsVar} = ${paramsObj};`);
|
|
1135
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${executeArg}, ${paramsVar});`);
|
|
1136
|
+
}
|
|
1137
|
+
else if (nodeType.scope || (nodeType.scopes && nodeType.scopes.length > 0)) {
|
|
1138
|
+
// Scoped node call with positional arguments (uses _impl signature)
|
|
1139
|
+
// Scoped nodes have callback parameters that can't be passed via params object
|
|
1140
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(', ')});`);
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
// Regular node call - always use positional args
|
|
1144
|
+
// In bundle mode we import _impl which takes (execute, ...positional_args)
|
|
1145
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(', ')});`);
|
|
1146
|
+
}
|
|
1147
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1148
|
+
if (isSuccessPort(portName) || isFailurePort(portName))
|
|
1149
|
+
return;
|
|
1150
|
+
// Skip scoped OUTPUT ports - they don't exist in the function return value
|
|
1151
|
+
const portConfig = nodeType.outputs[portName];
|
|
1152
|
+
if (portConfig.scope)
|
|
1153
|
+
return;
|
|
1154
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1155
|
+
});
|
|
1156
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
1157
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1158
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1159
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1160
|
+
lines.push(`${indent} status: 'SUCCEEDED',`);
|
|
1161
|
+
lines.push(`${indent} });`);
|
|
1162
|
+
lines.push(`${indent} } catch (error: unknown) {`);
|
|
1163
|
+
lines.push(`${indent} const isCancellation = CancellationError.isCancellationError(error);`);
|
|
1164
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
1165
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1166
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1167
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1168
|
+
lines.push(`${indent} status: isCancellation ? 'CANCELLED' : 'FAILED',`);
|
|
1169
|
+
lines.push(`${indent} });`);
|
|
1170
|
+
lines.push(`${indent} if (!isCancellation) {`);
|
|
1171
|
+
lines.push(`${indent} ${ctxVar}.sendLogErrorEvent({`);
|
|
1172
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1173
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1174
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1175
|
+
lines.push(`${indent} error: error instanceof Error ? error.message : String(error),`);
|
|
1176
|
+
lines.push(`${indent} });`);
|
|
1177
|
+
lines.push(`${indent} }`);
|
|
1178
|
+
lines.push(`${indent} throw error;`);
|
|
1179
|
+
lines.push(`${indent} }`);
|
|
1180
|
+
lines.push(`${indent}};`);
|
|
1181
|
+
lines.push(`${indent}// Register lazy executor for pull node`);
|
|
1182
|
+
lines.push(`${indent}${ctxVar}.registerPullExecutor('${instanceId}', ${safeId}_executor);`);
|
|
1183
|
+
lines.push(``);
|
|
1184
|
+
}
|
|
1185
|
+
function generateNodeCallWithContext(instance, nodeType, workflow, _availableVars, lines, _allNodeTypes, indent, isAsync, useConst = false, // Use const for nodes that always execute (not in branches)
|
|
1186
|
+
instanceParent, // Parent node ID for scope children (parent is const, no ! needed)
|
|
1187
|
+
ctxVar = 'ctx', // Context variable name (for scoped contexts)
|
|
1188
|
+
bundleMode = false, // Bundle mode uses params object pattern for wrapper functions
|
|
1189
|
+
skipExecuteGuard = false, // Skip execute port STEP guard (for nodes inside branch blocks)
|
|
1190
|
+
branchingNodes = new Set() // Branching nodes set for port-aware STEP guards
|
|
1191
|
+
) {
|
|
1192
|
+
const instanceId = instance.id;
|
|
1193
|
+
const safeId = toValidIdentifier(instanceId);
|
|
1194
|
+
const functionName = nodeType.functionName;
|
|
1195
|
+
// Check if this instance has pull execution enabled
|
|
1196
|
+
const fullInstance = workflow.instances.find((i) => i.id === instanceId);
|
|
1197
|
+
const pullConfig = fullInstance
|
|
1198
|
+
? getPullExecutionConfig(fullInstance, nodeType)
|
|
1199
|
+
: { enabled: false, triggerPort: 'execute' };
|
|
1200
|
+
// If this is a pull execution node, wrap it in a lazy function
|
|
1201
|
+
if (pullConfig.enabled) {
|
|
1202
|
+
generatePullNodeWithContext(instance, nodeType, workflow, lines, indent, isAsync, ctxVar, bundleMode);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const stepInputs = Object.entries(nodeType.inputs).filter(([portName, portConfig]) => {
|
|
1206
|
+
// Include the primary execute port for promoted nodes (useConst=false at top level)
|
|
1207
|
+
// so they get a STEP guard from their conditional source.
|
|
1208
|
+
// Skip execute guard for nodes inside branch blocks (execute is guaranteed by if/else).
|
|
1209
|
+
if (isExecutePort(portName) && !useConst && !skipExecuteGuard) {
|
|
1210
|
+
return portConfig.dataType === 'STEP';
|
|
1211
|
+
}
|
|
1212
|
+
return portConfig.dataType === 'STEP' && !isExecutePort(portName);
|
|
1213
|
+
});
|
|
1214
|
+
// Expression nodes don't declare 'execute' in their inputs, but the workflow
|
|
1215
|
+
// connects STEP signals to their execute port. Include it for promoted expression
|
|
1216
|
+
// nodes so they get a proper branch-aware STEP guard.
|
|
1217
|
+
if (nodeType.expression && !useConst && !skipExecuteGuard && !stepInputs.some(([p]) => isExecutePort(p))) {
|
|
1218
|
+
const hasExecuteConnection = workflow.connections.some((conn) => conn.to.node === instanceId && isExecutePort(conn.to.port));
|
|
1219
|
+
if (hasExecuteConnection) {
|
|
1220
|
+
stepInputs.push(['execute', { dataType: 'STEP' }]);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
const stepSignalSources = [];
|
|
1224
|
+
if (stepInputs.length > 0) {
|
|
1225
|
+
workflow.connections.forEach((conn) => {
|
|
1226
|
+
const toNode = conn.to.node;
|
|
1227
|
+
const toPort = conn.to.port;
|
|
1228
|
+
if (toNode === instanceId && stepInputs.some(([port]) => port === toPort)) {
|
|
1229
|
+
const sourceNode = conn.from.node;
|
|
1230
|
+
// Skip per-port scoped children - they don't have Idx in main scope
|
|
1231
|
+
const sourceInstance = workflow.instances.find((i) => i.id === sourceNode);
|
|
1232
|
+
if (sourceInstance && isPerPortScopedChild(sourceInstance, workflow, _allNodeTypes)) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (!isStartNode(sourceNode) && !stepSignalSources.includes(sourceNode)) {
|
|
1236
|
+
stepSignalSources.push(sourceNode);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
let shouldIndent = false;
|
|
1242
|
+
if (stepSignalSources.length > 0) {
|
|
1243
|
+
const conditions = [];
|
|
1244
|
+
const portToSources = new Map();
|
|
1245
|
+
stepInputs.forEach(([portName]) => {
|
|
1246
|
+
const sources = [];
|
|
1247
|
+
workflow.connections.forEach((conn) => {
|
|
1248
|
+
const toNode = conn.to.node;
|
|
1249
|
+
const toPort = conn.to.port;
|
|
1250
|
+
if (toNode === instanceId && toPort === portName) {
|
|
1251
|
+
const sourceNode = conn.from.node;
|
|
1252
|
+
if (!isStartNode(sourceNode)) {
|
|
1253
|
+
// Skip per-port scoped children - they don't have Idx in main scope
|
|
1254
|
+
const sourceInstance = workflow.instances.find((i) => i.id === sourceNode);
|
|
1255
|
+
if (sourceInstance && isPerPortScopedChild(sourceInstance, workflow, _allNodeTypes)) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
sources.push({ node: sourceNode, port: conn.from.port });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
if (sources.length > 0) {
|
|
1263
|
+
portToSources.set(portName, sources);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
if (nodeType.executeWhen === EXECUTION_STRATEGIES.CONJUNCTION) {
|
|
1267
|
+
// CONJUNCTION: Execute when ALL input ports have data (AND logic)
|
|
1268
|
+
portToSources.forEach((sources, _portName) => {
|
|
1269
|
+
if (sources.length === 1) {
|
|
1270
|
+
conditions.push(buildStepSourceCondition(sources[0].node, sources[0].port, branchingNodes));
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
const orCondition = sources.map((s) => buildStepSourceCondition(s.node, s.port, branchingNodes)).join(' || ');
|
|
1274
|
+
conditions.push(`(${orCondition})`);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
if (conditions.length > 0) {
|
|
1278
|
+
const fullCondition = conditions.join(' && ');
|
|
1279
|
+
lines.push(`${indent}if (${fullCondition}) {`);
|
|
1280
|
+
indent = `${indent} `;
|
|
1281
|
+
shouldIndent = true;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
else if (nodeType.executeWhen === EXECUTION_STRATEGIES.DISJUNCTION) {
|
|
1285
|
+
// DISJUNCTION: Execute when ANY input port has data (OR logic)
|
|
1286
|
+
const allSources = [];
|
|
1287
|
+
portToSources.forEach((sources) => {
|
|
1288
|
+
sources.forEach((source) => {
|
|
1289
|
+
if (!allSources.some((s) => s.node === source.node && s.port === source.port)) {
|
|
1290
|
+
allSources.push(source);
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
if (allSources.length > 0) {
|
|
1295
|
+
const fullCondition = allSources.map((s) => buildStepSourceCondition(s.node, s.port, branchingNodes)).join(' || ');
|
|
1296
|
+
lines.push(`${indent}if (${fullCondition}) {`);
|
|
1297
|
+
indent = `${indent} `;
|
|
1298
|
+
shouldIndent = true;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
else if (nodeType.executeWhen === EXECUTION_STRATEGIES.CUSTOM) {
|
|
1302
|
+
// CUSTOM: User-provided execution condition
|
|
1303
|
+
// Custom condition should be in nodeType.metadata.customExecuteCondition
|
|
1304
|
+
const customCondition = nodeType.metadata?.customExecuteCondition;
|
|
1305
|
+
if (customCondition && typeof customCondition === 'string') {
|
|
1306
|
+
lines.push(`${indent}if (${customCondition}) {`);
|
|
1307
|
+
indent = `${indent} `;
|
|
1308
|
+
shouldIndent = true;
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
// Fallback to CONJUNCTION if no custom condition provided
|
|
1312
|
+
portToSources.forEach((sources, _portName) => {
|
|
1313
|
+
if (sources.length === 1) {
|
|
1314
|
+
conditions.push(buildStepSourceCondition(sources[0].node, sources[0].port, branchingNodes));
|
|
1315
|
+
}
|
|
1316
|
+
else {
|
|
1317
|
+
const orCondition = sources.map((s) => buildStepSourceCondition(s.node, s.port, branchingNodes)).join(' || ');
|
|
1318
|
+
conditions.push(`(${orCondition})`);
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
if (conditions.length > 0) {
|
|
1322
|
+
const fullCondition = conditions.join(' && ');
|
|
1323
|
+
lines.push(`${indent}if (${fullCondition}) {`);
|
|
1324
|
+
indent = `${indent} `;
|
|
1325
|
+
shouldIndent = true;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const varDecl = useConst ? 'const ' : '';
|
|
1331
|
+
lines.push(`${indent}${ctxVar}.checkAborted('${instanceId}');`);
|
|
1332
|
+
lines.push(`${indent}${varDecl}${safeId}Idx = ${ctxVar}.addExecution('${instanceId}');`);
|
|
1333
|
+
lines.push(`${indent}${ctxVar}.sendStatusChangedEvent({`);
|
|
1334
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1335
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1336
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1337
|
+
lines.push(`${indent} status: 'RUNNING',`);
|
|
1338
|
+
lines.push(`${indent}});`);
|
|
1339
|
+
lines.push(`${indent}try {`);
|
|
1340
|
+
const getCall = isAsync ? `await ${ctxVar}.getVariable` : `${ctxVar}.getVariable`;
|
|
1341
|
+
const setCall = isAsync ? `await ${ctxVar}.setVariable` : `${ctxVar}.setVariable`;
|
|
1342
|
+
const args = buildNodeArgumentsWithContext({
|
|
1343
|
+
node: nodeType,
|
|
1344
|
+
workflow,
|
|
1345
|
+
id: instanceId,
|
|
1346
|
+
lines,
|
|
1347
|
+
indent: `${indent} `,
|
|
1348
|
+
getCall,
|
|
1349
|
+
isAsync,
|
|
1350
|
+
instanceParent,
|
|
1351
|
+
emitInputEvents: true,
|
|
1352
|
+
setCall,
|
|
1353
|
+
nodeTypeName: functionName,
|
|
1354
|
+
bundleMode,
|
|
1355
|
+
});
|
|
1356
|
+
const resultVar = `${safeId}Result`;
|
|
1357
|
+
const awaitKeyword = nodeType.isAsync ? 'await ' : '';
|
|
1358
|
+
if (nodeType.expression) {
|
|
1359
|
+
// Expression node: call without execute, map raw return to output ports
|
|
1360
|
+
// _impl returns data only; onSuccess/onFailure are auto-set in the workflow body
|
|
1361
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(', ')});`);
|
|
1362
|
+
// Determine data output ports (exclude control flow and scoped ports)
|
|
1363
|
+
const dataOutputPorts = Object.keys(nodeType.outputs).filter((portName) => {
|
|
1364
|
+
const portConfig = nodeType.outputs[portName];
|
|
1365
|
+
if (portConfig.scope)
|
|
1366
|
+
return false;
|
|
1367
|
+
if (isSuccessPort(portName) || isFailurePort(portName))
|
|
1368
|
+
return false;
|
|
1369
|
+
if (portConfig.isControlFlow || portConfig.failure)
|
|
1370
|
+
return false;
|
|
1371
|
+
return true;
|
|
1372
|
+
});
|
|
1373
|
+
if (dataOutputPorts.length === 1) {
|
|
1374
|
+
// Single data output: destructure if result is an object with the port key, else use raw value
|
|
1375
|
+
// Extract to unknown-typed variable to prevent TypeScript from narrowing
|
|
1376
|
+
// specific return types (e.g. boolean) to `never` in the typeof check
|
|
1377
|
+
const portName = dataOutputPorts[0];
|
|
1378
|
+
const rawVar = `${resultVar}_raw`;
|
|
1379
|
+
lines.push(`${indent} const ${rawVar}: unknown = ${resultVar};`);
|
|
1380
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, typeof ${rawVar} === 'object' && ${rawVar} !== null && '${portName}' in ${rawVar} ? ${rawVar}.${portName} : ${rawVar});`);
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
// Multiple data outputs: destructure from object return
|
|
1384
|
+
dataOutputPorts.forEach((portName) => {
|
|
1385
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
// Auto-set onSuccess/onFailure
|
|
1389
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`);
|
|
1390
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`);
|
|
1391
|
+
}
|
|
1392
|
+
else if (nodeType.variant === 'MAP_ITERATOR') {
|
|
1393
|
+
// MAP_ITERATOR: inline iteration — no user function to call
|
|
1394
|
+
// args: [execute, items, scopeFn]
|
|
1395
|
+
const executeArg = args[0];
|
|
1396
|
+
const itemsArg = args[1];
|
|
1397
|
+
const scopeFnArg = args[2];
|
|
1398
|
+
lines.push(`${indent} let ${resultVar}: { onSuccess: boolean; onFailure: boolean; results: unknown[] };`);
|
|
1399
|
+
lines.push(`${indent} if (!${executeArg}) {`);
|
|
1400
|
+
lines.push(`${indent} ${resultVar} = { onSuccess: false, onFailure: false, results: [] };`);
|
|
1401
|
+
lines.push(`${indent} } else {`);
|
|
1402
|
+
lines.push(`${indent} const __results: unknown[] = [];`);
|
|
1403
|
+
lines.push(`${indent} for (const __item of ${itemsArg}) {`);
|
|
1404
|
+
lines.push(`${indent} __results.push((${isAsync ? 'await ' : ''}${scopeFnArg}(true, __item)).processed);`);
|
|
1405
|
+
lines.push(`${indent} }`);
|
|
1406
|
+
lines.push(`${indent} ${resultVar} = { onSuccess: true, onFailure: false, results: __results };`);
|
|
1407
|
+
lines.push(`${indent} }`);
|
|
1408
|
+
// Set output ports from result
|
|
1409
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1410
|
+
const portConfig = nodeType.outputs[portName];
|
|
1411
|
+
if (portConfig.scope)
|
|
1412
|
+
return;
|
|
1413
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
else if (nodeType.variant === 'IMPORTED_WORKFLOW' || nodeType.variant === 'WORKFLOW') {
|
|
1417
|
+
// Check if this is a workflow call (IMPORTED_WORKFLOW or WORKFLOW variant)
|
|
1418
|
+
// Workflows use (execute, params) signature where params is an object
|
|
1419
|
+
// Regular nodes use (execute, arg1, arg2, ...) positional signature
|
|
1420
|
+
// For workflow calls, wrap data args in an object and include recursion depth
|
|
1421
|
+
const executeArg = args[0]; // First arg is always execute
|
|
1422
|
+
const dataArgs = args.slice(1); // Rest are data inputs
|
|
1423
|
+
const inputPortNames = Object.keys(nodeType.inputs).filter((p) => !isExecutePort(p));
|
|
1424
|
+
// Build params object: { portName1: value1, portName2: value2, ..., __rd__: __rd__ + 1 }
|
|
1425
|
+
// Assign to variable first to avoid TypeScript excess property checking on object literals
|
|
1426
|
+
const paramsEntries = inputPortNames.map((portName, i) => `${portName}: ${dataArgs[i]}`);
|
|
1427
|
+
paramsEntries.push('__rd__: __rd__ + 1');
|
|
1428
|
+
const paramsObj = `{ ${paramsEntries.join(', ')} }`;
|
|
1429
|
+
const paramsVar = `__${safeId}Params__`;
|
|
1430
|
+
lines.push(`${indent} const ${paramsVar} = ${paramsObj};`);
|
|
1431
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${executeArg}, ${paramsVar});`);
|
|
1432
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
1433
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
1434
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1435
|
+
const portConfig = nodeType.outputs[portName];
|
|
1436
|
+
// Skip scoped OUTPUT ports - they don't exist in the function return value
|
|
1437
|
+
if (portConfig.scope)
|
|
1438
|
+
return;
|
|
1439
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
else if (nodeType.scope || (nodeType.scopes && nodeType.scopes.length > 0)) {
|
|
1443
|
+
// Scoped node call with positional arguments (uses _impl signature)
|
|
1444
|
+
// Scoped nodes have callback parameters that can't be passed via params object
|
|
1445
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(', ')});`);
|
|
1446
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
1447
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
1448
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1449
|
+
const portConfig = nodeType.outputs[portName];
|
|
1450
|
+
// Skip scoped OUTPUT ports - they don't exist in the function return value
|
|
1451
|
+
if (portConfig.scope)
|
|
1452
|
+
return;
|
|
1453
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
// Regular node call - always use positional args
|
|
1458
|
+
// In bundle mode we import _impl which takes (execute, ...positional_args)
|
|
1459
|
+
lines.push(`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(', ')});`);
|
|
1460
|
+
// STEP Port Architecture: Extract ALL outputs from result, including onSuccess/onFailure
|
|
1461
|
+
// Skip scoped OUTPUT ports - they're parameters to scope functions, not return values
|
|
1462
|
+
Object.keys(nodeType.outputs).forEach((portName) => {
|
|
1463
|
+
const portConfig = nodeType.outputs[portName];
|
|
1464
|
+
// Skip scoped OUTPUT ports - they don't exist in the function return value
|
|
1465
|
+
if (portConfig.scope)
|
|
1466
|
+
return;
|
|
1467
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: '${portName}', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar}.${portName});`);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
1471
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1472
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1473
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1474
|
+
lines.push(`${indent} status: 'SUCCEEDED',`);
|
|
1475
|
+
lines.push(`${indent} });`);
|
|
1476
|
+
lines.push(`${indent}} catch (error: unknown) {`);
|
|
1477
|
+
lines.push(`${indent} const isCancellation = CancellationError.isCancellationError(error);`);
|
|
1478
|
+
lines.push(`${indent} ${ctxVar}.sendStatusChangedEvent({`);
|
|
1479
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1480
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1481
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1482
|
+
lines.push(`${indent} status: isCancellation ? 'CANCELLED' : 'FAILED',`);
|
|
1483
|
+
lines.push(`${indent} });`);
|
|
1484
|
+
lines.push(`${indent} if (!isCancellation) {`);
|
|
1485
|
+
lines.push(`${indent} ${ctxVar}.sendLogErrorEvent({`);
|
|
1486
|
+
lines.push(`${indent} nodeTypeName: '${functionName}',`);
|
|
1487
|
+
lines.push(`${indent} id: '${instanceId}',`);
|
|
1488
|
+
lines.push(`${indent} executionIndex: ${safeId}Idx,`);
|
|
1489
|
+
lines.push(`${indent} error: error instanceof Error ? error.message : String(error),`);
|
|
1490
|
+
lines.push(`${indent} });`);
|
|
1491
|
+
if (nodeType.expression) {
|
|
1492
|
+
// Expression node: auto-set failure flags in catch block
|
|
1493
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`);
|
|
1494
|
+
lines.push(`${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`);
|
|
1495
|
+
}
|
|
1496
|
+
lines.push(`${indent} }`);
|
|
1497
|
+
lines.push(`${indent} throw error;`);
|
|
1498
|
+
lines.push(`${indent}}`);
|
|
1499
|
+
if (shouldIndent) {
|
|
1500
|
+
const originalIndent = indent.slice(0, -2);
|
|
1501
|
+
lines.push(`${originalIndent}}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
//# sourceMappingURL=unified.js.map
|