@synergenius/flow-weaver 0.33.2 → 0.44.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 +201 -226
- package/NOTICE +13 -0
- package/README.md +152 -245
- package/dist/agent/agent-loop.d.ts +4 -2
- package/dist/agent/cli-session.d.ts +3 -3
- package/dist/agent/cli-session.js +511 -438
- package/dist/agent/cli-spawn-config.d.ts +4 -4
- package/dist/agent/gate.d.ts +164 -0
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +174 -28
- package/dist/agent/mcp-bridge.d.ts +1 -1
- package/dist/agent/mcp-bridge.js +114 -128
- package/dist/agent/mcp-tool-server.d.ts +2 -2
- package/dist/agent/mcp-tool-server.js +147 -189
- package/dist/agent/profiles.d.ts +69 -0
- package/dist/agent/providers/anthropic.d.ts +2 -4
- package/dist/agent/providers/claude-cli.d.ts +4 -5
- package/dist/agent/providers/openai-compat.d.ts +8 -1
- package/dist/agent/streaming.d.ts +2 -2
- package/dist/agent/types.d.ts +16 -10
- package/dist/api/builder.d.ts +3 -3
- package/dist/api/compile.d.ts +9 -4
- package/dist/api/durable-validation.d.ts +15 -0
- package/dist/api/generate-in-place.d.ts +21 -19
- package/dist/api/generate.d.ts +21 -16
- package/dist/api/generated-module/local-node-functions.d.ts +24 -0
- package/dist/api/generated-module/module-format.d.ts +23 -0
- package/dist/api/generated-module/module-writer.d.ts +34 -0
- package/dist/api/generated-module/node-type-imports.d.ts +39 -0
- package/dist/api/generated-module/preamble.d.ts +16 -0
- package/dist/api/generated-module/workflow-functions.d.ts +29 -0
- package/dist/api/graph-identity.d.ts +39 -0
- package/dist/api/helpers.d.ts +4 -60
- package/dist/api/in-place/built-in-nodes.d.ts +18 -0
- package/dist/api/in-place/function-body.d.ts +21 -0
- package/dist/api/in-place/fw-imports.d.ts +30 -0
- package/dist/api/in-place/macro-coverage.d.ts +14 -0
- package/dist/api/in-place/node-type-functions.d.ts +18 -0
- package/dist/api/in-place/node-type-jsdoc.d.ts +20 -0
- package/dist/api/in-place/orphaned-node-types.d.ts +16 -0
- package/dist/api/in-place/runtime-section.d.ts +16 -0
- package/dist/api/in-place/signature.d.ts +47 -0
- package/dist/api/in-place/source-file.d.ts +19 -0
- package/dist/api/in-place/workflow-jsdoc.d.ts +19 -0
- package/dist/api/index.d.ts +18 -21
- package/dist/api/index.js +259 -53
- package/dist/api/inline-engine.generated.d.ts +3 -0
- package/dist/api/inline-runtime.d.ts +10 -2
- package/dist/api/manipulation/connections.d.ts +1 -1
- package/dist/api/manipulation/index.d.ts +8 -8
- package/dist/api/manipulation/node-types.d.ts +1 -1
- package/dist/api/manipulation/nodes.d.ts +3 -13
- package/dist/api/manipulation/ports.d.ts +1 -1
- package/dist/api/manipulation/scopes.d.ts +1 -1
- package/dist/api/manipulation/workflow.d.ts +1 -1
- package/dist/api/modify-operation.d.ts +0 -2
- package/dist/api/parse.d.ts +22 -1
- package/dist/api/port-references.d.ts +66 -0
- package/dist/api/query.d.ts +18 -2
- package/dist/api/templates.d.ts +1 -1
- package/dist/api/transform.d.ts +1 -1
- package/dist/api/validate.d.ts +1 -1
- package/dist/api/validation-registry.d.ts +2 -2
- package/dist/artifacts/brief.d.ts +43 -0
- package/dist/artifacts/index.d.ts +31 -0
- package/dist/artifacts/pdf.d.ts +19 -0
- package/dist/ast/builder.d.ts +3 -3
- package/dist/ast/index.d.ts +4 -4
- package/dist/ast/index.js +60 -5
- package/dist/ast/serialization-node.d.ts +1 -1
- package/dist/ast/serialization.d.ts +1 -1
- package/dist/ast/types.d.ts +82 -291
- package/dist/ast/workflow-utils.d.ts +4 -4
- package/dist/built-in-nodes/coercion-types.d.ts +1 -1
- package/dist/built-in-nodes/delay.d.ts +3 -1
- package/dist/built-in-nodes/generated-registry.d.ts +1 -1
- package/dist/built-in-nodes/index.d.ts +6 -5
- package/dist/built-in-nodes/index.js +79 -5
- package/dist/built-in-nodes/invoke-workflow.d.ts +2 -1
- package/dist/built-in-nodes/mock-types.d.ts +45 -14
- package/dist/built-in-nodes/sleep.d.ts +12 -0
- package/dist/built-in-nodes/wait-for-agent.d.ts +2 -1
- package/dist/built-in-nodes/wait-for-event.d.ts +2 -1
- package/dist/chevrotain-parser/coerce-parser.d.ts +1 -1
- package/dist/chevrotain-parser/connect-parser.d.ts +1 -1
- package/dist/chevrotain-parser/grammar-diagrams.d.ts +1 -1
- package/dist/chevrotain-parser/index.d.ts +20 -23
- package/dist/chevrotain-parser/node-parser.d.ts +7 -8
- package/dist/chevrotain-parser/parse-line.d.ts +54 -0
- package/dist/chevrotain-parser/tokens.d.ts +0 -17
- package/dist/chevrotain-parser/trigger-cancel-parser.d.ts +6 -1
- package/dist/chunks/chunk-4EUSHAY6.js +298 -0
- package/dist/chunks/chunk-4KDUEOFD.js +197 -0
- package/dist/chunks/chunk-5Y3AIUWY.js +911 -0
- package/dist/chunks/chunk-6KMVP6AU.js +20 -0
- package/dist/chunks/chunk-6PNJORFL.js +128 -0
- package/dist/chunks/chunk-7AAQNGRH.js +2001 -0
- package/dist/chunks/chunk-7YYKTPJ5.js +22705 -0
- package/dist/chunks/chunk-A5MBOFVY.js +33 -0
- package/dist/chunks/chunk-AXNQRVJB.js +18 -0
- package/dist/chunks/chunk-B6HQ6YS5.js +572 -0
- package/dist/chunks/chunk-CY3RNVML.js +296 -0
- package/dist/chunks/chunk-CYHYDLV4.js +653 -0
- package/dist/chunks/chunk-CZBCCK4Y.js +82 -0
- package/dist/chunks/chunk-DCMGQP5V.js +505 -0
- package/dist/chunks/chunk-DIAA66GL.js +72 -0
- package/dist/chunks/chunk-DUO6H5PR.js +20 -0
- package/dist/chunks/chunk-E5WN7IGO.js +125 -0
- package/dist/chunks/chunk-EFGFXXQF.js +184 -0
- package/dist/chunks/chunk-F3RTZV2A.js +2811 -0
- package/dist/chunks/chunk-FMTLP5ZX.js +1397 -0
- package/dist/chunks/chunk-HKZLQJDV.js +79 -0
- package/dist/chunks/chunk-INDEVYEC.js +146 -0
- package/dist/chunks/chunk-JQTF4R5L.js +390 -0
- package/dist/chunks/chunk-KMCAYNUM.js +331 -0
- package/dist/chunks/chunk-KMWCCOUJ.js +1444 -0
- package/dist/chunks/chunk-KU3CLJI2.js +338 -0
- package/dist/chunks/chunk-NDSTGMKR.js +3300 -0
- package/dist/chunks/chunk-NQNLUAPO.js +505 -0
- package/dist/chunks/chunk-O2U2LO57.js +466 -0
- package/dist/chunks/chunk-OOQG7UPP.js +457 -0
- package/dist/chunks/chunk-OZQAW5ZG.js +112 -0
- package/dist/chunks/chunk-P2CVDGHR.js +487 -0
- package/dist/chunks/chunk-P44RIZ7C.js +592 -0
- package/dist/chunks/chunk-PEZCT37T.js +1475 -0
- package/dist/chunks/chunk-PKWSAW6O.js +152 -0
- package/dist/chunks/chunk-Q6MDDZMT.js +4373 -0
- package/dist/chunks/chunk-QDE4Z7KW.js +6 -0
- package/dist/chunks/chunk-QJDAVSNY.js +8 -0
- package/dist/chunks/chunk-QYPORSLO.js +7552 -0
- package/dist/chunks/chunk-R4QGNK34.js +183 -0
- package/dist/chunks/chunk-REEYC73I.js +3002 -0
- package/dist/chunks/chunk-SEWDMQ6Q.js +46 -0
- package/dist/chunks/chunk-TAIUWKZB.js +3057 -0
- package/dist/chunks/chunk-TN4YZ3O7.js +196 -0
- package/dist/chunks/chunk-TRMFB43C.js +143 -0
- package/dist/chunks/chunk-VO4QXG2X.js +1016 -0
- package/dist/chunks/chunk-VP2P3BPA.js +1002 -0
- package/dist/chunks/chunk-VQ7H33J4.js +2176 -0
- package/dist/chunks/chunk-VWA7MCV2.js +105 -0
- package/dist/chunks/chunk-XAV22WD7.js +253 -0
- package/dist/chunks/chunk-YSPBPZDV.js +148 -0
- package/dist/chunks/chunk-ZKEGXO73.js +282 -0
- package/dist/chunks/chunk-ZMGN6F3S.js +4256 -0
- package/dist/chunks/grammar-diagrams-AI7VDXOM.js +14 -0
- package/dist/chunks/index.min-ECJD6GZC.js +37 -0
- package/dist/chunks/manifest-EPTR5MYH.js +21 -0
- package/dist/chunks/openapi-document-6TCGVDUW.js +69 -0
- package/dist/chunks/registry-K2U5KUTY.js +23 -0
- package/dist/chunks/serialization-node-UGY6CFH6.js +38 -0
- package/dist/chunks/validate-GEKSZPIN.js +24 -0
- package/dist/chunks/validation-registry-IQZX3UE3.js +7 -0
- package/dist/chunks/validator-VXFHLPDW.js +26 -0
- package/dist/chunks/workflow-executor-KBGFKHNR.js +36 -0
- package/dist/cli/commands/agents.d.ts +16 -0
- package/dist/cli/commands/artifact.d.ts +10 -0
- package/dist/cli/commands/compile.d.ts +0 -30
- package/dist/cli/commands/console.d.ts +32 -0
- package/dist/cli/commands/context.d.ts +0 -3
- package/dist/cli/commands/describe.js +386 -352
- package/dist/cli/commands/dev.d.ts +9 -4
- package/dist/cli/commands/diagram.d.ts +3 -5
- package/dist/cli/commands/doctor.d.ts +27 -4
- package/dist/cli/commands/doctor.js +3307 -633
- package/dist/cli/commands/export.d.ts +6 -4
- package/dist/cli/commands/grammar.d.ts +0 -3
- package/dist/cli/commands/implement.d.ts +1 -1
- package/dist/cli/commands/init/agent-handoff.d.ts +28 -0
- package/dist/cli/commands/init/config.d.ts +3 -0
- package/dist/cli/commands/init/files.d.ts +12 -0
- package/dist/cli/commands/init/naming.d.ts +13 -0
- package/dist/cli/commands/init/output.d.ts +12 -0
- package/dist/cli/commands/init/setup.d.ts +21 -0
- package/dist/cli/commands/init/types.d.ts +53 -0
- package/dist/cli/commands/init-personas.d.ts +9 -1
- package/dist/cli/commands/init.d.ts +24 -88
- package/dist/cli/commands/market.d.ts +1 -1
- package/dist/cli/commands/mcp-setup.d.ts +1 -4
- package/dist/cli/commands/modify.d.ts +0 -5
- package/dist/cli/commands/openapi.d.ts +9 -11
- package/dist/cli/commands/run.d.ts +0 -4
- package/dist/cli/commands/serve.d.ts +24 -26
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/exports.d.ts +3 -4
- package/dist/cli/exports.js +124 -23
- package/dist/cli/flow-weaver-agents-H653446S.mjs +81 -0
- package/dist/cli/flow-weaver-artifact-WBFYTW76.mjs +58 -0
- package/dist/cli/flow-weaver-chunk-265CEK74.mjs +2 -0
- package/dist/cli/flow-weaver-chunk-2C7PZCV5.mjs +186 -0
- package/dist/cli/flow-weaver-chunk-3BSYCWPL.mjs +3109 -0
- package/dist/cli/flow-weaver-chunk-3MKUQP3D.mjs +547 -0
- package/dist/cli/flow-weaver-chunk-3ZLMPQTH.mjs +27 -0
- package/dist/cli/flow-weaver-chunk-4DVY73ML.mjs +280 -0
- package/dist/cli/flow-weaver-chunk-54M4KNEL.mjs +200 -0
- package/dist/cli/flow-weaver-chunk-55QMUZIA.mjs +256 -0
- package/dist/cli/flow-weaver-chunk-6ZUJ7LMX.mjs +2003 -0
- package/dist/cli/flow-weaver-chunk-7ET6N32V.mjs +419 -0
- package/dist/cli/flow-weaver-chunk-7G63RR4K.mjs +8880 -0
- package/dist/cli/flow-weaver-chunk-A6M5K3W7.mjs +4418 -0
- package/dist/cli/flow-weaver-chunk-AA3NATMX.mjs +154 -0
- package/dist/cli/flow-weaver-chunk-APX6MQ5A.mjs +5652 -0
- package/dist/cli/flow-weaver-chunk-BHCC5FE5.mjs +17 -0
- package/dist/cli/flow-weaver-chunk-DM4BBRRU.mjs +35 -0
- package/dist/cli/flow-weaver-chunk-DRN7KPWY.mjs +168 -0
- package/dist/cli/flow-weaver-chunk-EKWF27G6.mjs +23 -0
- package/dist/cli/flow-weaver-chunk-EM5HWQ4R.mjs +338 -0
- package/dist/cli/flow-weaver-chunk-FPPPU2DG.mjs +226 -0
- package/dist/cli/flow-weaver-chunk-GAQXZHNO.mjs +3005 -0
- package/dist/cli/flow-weaver-chunk-GBUNNP33.mjs +1329 -0
- package/dist/cli/flow-weaver-chunk-HMAKDE55.mjs +636 -0
- package/dist/cli/flow-weaver-chunk-HQ3ZAN44.mjs +367 -0
- package/dist/cli/flow-weaver-chunk-I4APCXYN.mjs +21442 -0
- package/dist/cli/flow-weaver-chunk-IJJ4JXTL.mjs +2297 -0
- package/dist/cli/flow-weaver-chunk-J7EPZW2I.mjs +33 -0
- package/dist/cli/flow-weaver-chunk-KOEMJHLG.mjs +83 -0
- package/dist/cli/flow-weaver-chunk-M6CBWQKJ.mjs +271 -0
- package/dist/cli/flow-weaver-chunk-MHJ4N652.mjs +1580 -0
- package/dist/cli/flow-weaver-chunk-MKAFQWLP.mjs +202 -0
- package/dist/cli/flow-weaver-chunk-MMV2XGD3.mjs +3291 -0
- package/dist/cli/flow-weaver-chunk-MSI2UJES.mjs +125 -0
- package/dist/cli/flow-weaver-chunk-N767YRAI.mjs +48 -0
- package/dist/cli/flow-weaver-chunk-NQKKWDGF.mjs +56 -0
- package/dist/cli/flow-weaver-chunk-NYZDQYFU.mjs +45 -0
- package/dist/cli/flow-weaver-chunk-OMVWSNHJ.mjs +14 -0
- package/dist/cli/flow-weaver-chunk-PUWZ47H5.mjs +2188 -0
- package/dist/cli/flow-weaver-chunk-QOPRVDES.mjs +46 -0
- package/dist/cli/flow-weaver-chunk-RIWE7YTR.mjs +216 -0
- package/dist/cli/flow-weaver-chunk-S63Q3JIU.mjs +789 -0
- package/dist/cli/flow-weaver-chunk-T7X5A23H.mjs +558 -0
- package/dist/cli/flow-weaver-chunk-TLV3VZUN.mjs +2 -0
- package/dist/cli/flow-weaver-chunk-TS5P3PIN.mjs +194 -0
- package/dist/cli/flow-weaver-chunk-U3EO3X4B.mjs +649 -0
- package/dist/cli/flow-weaver-chunk-UNFS3NQP.mjs +34 -0
- package/dist/cli/flow-weaver-chunk-V6DHWFQG.mjs +2811 -0
- package/dist/cli/flow-weaver-chunk-VXWNLYQU.mjs +9 -0
- package/dist/cli/flow-weaver-chunk-W3YNK26P.mjs +149 -0
- package/dist/cli/flow-weaver-chunk-XG2WKXYZ.mjs +324 -0
- package/dist/cli/flow-weaver-chunk-YLSRZWFK.mjs +2219 -0
- package/dist/cli/flow-weaver-chunk-YVPURWS6.mjs +40 -0
- package/dist/cli/flow-weaver-chunk-Z44JK332.mjs +582 -0
- package/dist/cli/flow-weaver-chunk-Z5ZJPNSY.mjs +517 -0
- package/dist/cli/flow-weaver-chunk-ZQALO25T.mjs +594 -0
- package/dist/cli/flow-weaver-compile-A2CABDSB.mjs +37 -0
- package/dist/cli/flow-weaver-console-6EBHDBTN.mjs +71 -0
- package/dist/cli/flow-weaver-context-ECLYANFF.mjs +17 -0
- package/dist/cli/flow-weaver-context-ZRUMW2KE.mjs +69 -0
- package/dist/cli/flow-weaver-create-WIP4EFNP.mjs +124 -0
- package/dist/cli/flow-weaver-deployment-WJJRK7NO.mjs +14 -0
- package/dist/cli/flow-weaver-describe-XDIFF5LI.mjs +41 -0
- package/dist/cli/flow-weaver-dev-LG2NSQAU.mjs +196 -0
- package/dist/cli/flow-weaver-diagram-3EVQARQE.mjs +52 -0
- package/dist/cli/flow-weaver-diagram-7LR7O4M2.mjs +44 -0
- package/dist/cli/flow-weaver-diff-USF7DJLU.mjs +85 -0
- package/dist/cli/flow-weaver-docs-QU2ZOXKH.mjs +96 -0
- package/dist/cli/flow-weaver-doctor-K447YWHR.mjs +43 -0
- package/dist/cli/flow-weaver-export-LAS6N56Y.mjs +140 -0
- package/dist/cli/flow-weaver-grammar-RIRUS3Q6.mjs +49 -0
- package/dist/cli/flow-weaver-grammar-diagrams-WXRTW5SK.mjs +15 -0
- package/dist/cli/flow-weaver-implement-5JG6BCFZ.mjs +114 -0
- package/dist/cli/flow-weaver-index.min-OR5SIHXD.mjs +39 -0
- package/dist/cli/flow-weaver-init-34YZNPQX.mjs +972 -0
- package/dist/cli/flow-weaver-init-personas-NOGZTQQ2.mjs +48 -0
- package/dist/cli/flow-weaver-manifest-4JNUSEB3.mjs +18 -0
- package/dist/cli/flow-weaver-market-WA4CKHKV.mjs +444 -0
- package/dist/cli/flow-weaver-mcp-setup-O26MGXSD.mjs +22 -0
- package/dist/cli/flow-weaver-migrate-VFVEUTPH.mjs +119 -0
- package/dist/cli/flow-weaver-modify-VM3JP5K6.mjs +97 -0
- package/dist/cli/flow-weaver-openapi-IRBWI3HM.mjs +71 -0
- package/dist/cli/flow-weaver-openapi-document-7N6YMPG3.mjs +30 -0
- package/dist/cli/flow-weaver-pack-commands-OS2QRXAK.mjs +146 -0
- package/dist/cli/flow-weaver-registry-5MLFRPPT.mjs +25 -0
- package/dist/cli/flow-weaver-run-DSTLSTHR.mjs +555 -0
- package/dist/cli/flow-weaver-serialization-node-LE6PCG4M.mjs +50 -0
- package/dist/cli/flow-weaver-serve-NVLCLSOW.mjs +192 -0
- package/dist/cli/flow-weaver-server-7IERLVXE.mjs +2925 -0
- package/dist/cli/flow-weaver-server-KWF4PXYC.mjs +19112 -0
- package/dist/cli/flow-weaver-status-75W2S3YB.mjs +138 -0
- package/dist/cli/flow-weaver-strip-AZPXUYVA.mjs +79 -0
- package/dist/cli/flow-weaver-templates-DSNMB6K7.mjs +72 -0
- package/dist/cli/flow-weaver-validate-A4UWDQN6.mjs +19 -0
- package/dist/cli/flow-weaver-validate-GRBZ5W4C.mjs +296 -0
- package/dist/cli/flow-weaver-validation-registry-BINGUNXX.mjs +9 -0
- package/dist/cli/flow-weaver-validator-TY56CI42.mjs +21 -0
- package/dist/cli/flow-weaver-watch-OPPERXL6.mjs +114 -0
- package/dist/cli/flow-weaver-workflow-executor-6AHYPXAN.mjs +26 -0
- package/dist/cli/flow-weaver.mjs +3557 -89384
- package/dist/cli/index.d.ts +5 -2
- package/dist/cli/pack-commands-needed.d.ts +12 -0
- package/dist/cli/program.d.ts +14 -0
- package/dist/cli/templates/ai/mock-provider.d.ts +1 -1
- package/dist/cli/templates/index.d.ts +1 -8
- package/dist/cli/templates/nodes/agent-router.d.ts +1 -1
- package/dist/cli/templates/nodes/aggregator.d.ts +1 -1
- package/dist/cli/templates/nodes/conversation-memory.d.ts +1 -1
- package/dist/cli/templates/nodes/http.d.ts +1 -1
- package/dist/cli/templates/nodes/human-approval.d.ts +1 -1
- package/dist/cli/templates/nodes/json-extractor.d.ts +1 -1
- package/dist/cli/templates/nodes/llm-call.d.ts +1 -1
- package/dist/cli/templates/nodes/prompt-template.d.ts +1 -1
- package/dist/cli/templates/nodes/rag-retriever.d.ts +1 -1
- package/dist/cli/templates/nodes/tool-executor.d.ts +1 -1
- package/dist/cli/templates/nodes/transformer.d.ts +1 -1
- package/dist/cli/templates/nodes/validator.d.ts +1 -1
- package/dist/cli/templates/shared/approval-types.d.ts +1 -1
- package/dist/cli/templates/shared/case.d.ts +14 -0
- package/dist/cli/templates/shared/llm-types.d.ts +4 -4
- package/dist/cli/templates/workflows/aggregator.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-agent.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-chat.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-rag.d.ts +1 -1
- package/dist/cli/templates/workflows/ai-react.d.ts +1 -1
- package/dist/cli/templates/workflows/approval.d.ts +10 -0
- package/dist/cli/templates/workflows/conditional.d.ts +2 -2
- package/dist/cli/templates/workflows/error-handler.d.ts +2 -2
- package/dist/cli/templates/workflows/foreach.d.ts +1 -1
- package/dist/cli/templates/workflows/sequential.d.ts +1 -1
- package/dist/cli/templates/workflows/webhook.d.ts +1 -1
- package/dist/cli/utils/interactive.d.ts +6 -0
- package/dist/cli/utils/json-option.d.ts +8 -0
- package/dist/cli/utils/logger.d.ts +2 -0
- package/dist/compiler/durable-source-proof.d.ts +23 -0
- package/dist/compiler/executable-artifact.d.ts +29 -0
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/index.js +36 -0
- package/dist/console/app.js +106 -0
- package/dist/console/assets/synergenius.svg +18 -0
- package/dist/console/author.d.ts +32 -0
- package/dist/console/cli-catalog.d.ts +21 -0
- package/dist/console/cli-run.d.ts +35 -0
- package/dist/console/cli-run.js +46 -0
- package/dist/console/debug.d.ts +67 -0
- package/dist/console/diff-view.d.ts +96 -0
- package/dist/console/export.d.ts +36 -0
- package/dist/console/git.d.ts +27 -0
- package/dist/console/index.d.ts +4 -0
- package/dist/console/index.html +16 -0
- package/dist/console/index.js +203 -0
- package/dist/console/packs.d.ts +100 -0
- package/dist/console/request-guard.d.ts +10 -0
- package/dist/console/respond.d.ts +16 -0
- package/dist/console/router.d.ts +64 -0
- package/dist/console/routes-agents.d.ts +14 -0
- package/dist/console/routes-project.d.ts +3 -0
- package/dist/console/routes-runs.d.ts +3 -0
- package/dist/console/routes-services.d.ts +7 -0
- package/dist/console/routes-tools.d.ts +3 -0
- package/dist/console/runs.d.ts +69 -0
- package/dist/console/scan.d.ts +49 -0
- package/dist/console/schema.d.ts +74 -0
- package/dist/console/server.d.ts +34 -0
- package/dist/console/server.js +43264 -0
- package/dist/console/services.d.ts +119 -0
- package/dist/console/source.d.ts +23 -0
- package/dist/console/status.d.ts +68 -0
- package/dist/console/styles.css +1023 -0
- package/dist/console/terminals.d.ts +22 -0
- package/dist/console/workflow-view.d.ts +153 -0
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +56 -167
- package/dist/context/index.d.ts +10 -0
- package/dist/context/index.js +14 -191
- package/dist/coordinator/agent-gate.d.ts +107 -0
- package/dist/coordinator/bundle-digest.d.ts +20 -0
- package/dist/coordinator/clock-tick.d.ts +4 -0
- package/dist/coordinator/effect-receipts.d.ts +10 -0
- package/dist/coordinator/errors.d.ts +31 -0
- package/dist/coordinator/file-store.d.ts +3 -0
- package/dist/coordinator/gate-labeling.d.ts +33 -0
- package/dist/coordinator/gate-resolution.d.ts +28 -0
- package/dist/coordinator/index.d.ts +12 -0
- package/dist/coordinator/index.js +107 -0
- package/dist/coordinator/memory-store.d.ts +3 -0
- package/dist/coordinator/params.d.ts +18 -0
- package/dist/coordinator/run-annotations.d.ts +6 -0
- package/dist/coordinator/run-context.d.ts +11 -0
- package/dist/coordinator/run-ending.d.ts +5 -0
- package/dist/coordinator/run-outcome.d.ts +31 -0
- package/dist/coordinator/run-resume.d.ts +6 -0
- package/dist/coordinator/run-start.d.ts +4 -0
- package/dist/coordinator/run-store.d.ts +275 -0
- package/dist/coordinator/run-trace.d.ts +41 -0
- package/dist/coordinator/run-views.d.ts +12 -0
- package/dist/coordinator/runs-dir.d.ts +26 -0
- package/dist/coordinator/store.d.ts +67 -0
- package/dist/coordinator/time.d.ts +39 -0
- package/dist/coordinator/workflow-selection.d.ts +6 -0
- package/dist/deployment/index.d.ts +11 -16
- package/dist/deployment/index.js +541 -50
- package/dist/deployment/targets/base.d.ts +11 -13
- package/dist/deployment/targets/openapi-parts.d.ts +92 -0
- package/dist/diagram/arms.d.ts +70 -0
- package/dist/diagram/ascii-renderer.d.ts +1 -1
- package/dist/diagram/geometry.d.ts +20 -32
- package/dist/diagram/graph/bounds.d.ts +30 -0
- package/dist/diagram/graph/metrics.d.ts +21 -0
- package/dist/diagram/graph/nodes.d.ts +33 -0
- package/dist/diagram/graph/placement.d.ts +22 -0
- package/dist/diagram/graph/ports.d.ts +23 -0
- package/dist/diagram/graph/routing.d.ts +40 -0
- package/dist/diagram/graph/scopes.d.ts +30 -0
- package/dist/diagram/index.d.ts +15 -23
- package/dist/diagram/index.js +632 -111
- package/dist/diagram/labels.d.ts +12 -0
- package/dist/diagram/lanes.d.ts +93 -0
- package/dist/diagram/layout.d.ts +1 -1
- package/dist/diagram/material-symbols.d.ts +7 -0
- package/dist/diagram/orthogonal-router.d.ts +2 -3
- package/dist/diagram/process-view.d.ts +79 -0
- package/dist/diagram/spine.d.ts +25 -0
- package/dist/diagram/theme.d.ts +1 -5
- package/dist/diagram/types.d.ts +6 -20
- package/dist/diff/index.js +53 -9
- package/dist/diff/types.d.ts +1 -5
- package/dist/doc-metadata/extractors/annotations.d.ts +0 -4
- package/dist/doc-metadata/extractors/cli-commands.d.ts +2 -1
- package/dist/doc-metadata/extractors/core-metadata.d.ts +19 -4
- package/dist/doc-metadata/extractors/error-codes.d.ts +1 -1
- package/dist/doc-metadata/extractors/mcp-tools.d.ts +7 -3
- package/dist/doc-metadata/index.d.ts +1 -2
- package/dist/doc-metadata/index.js +2368 -9
- package/dist/doc-metadata/types.d.ts +7 -24
- package/dist/docs/guide.d.ts +33 -0
- package/dist/docs/index.d.ts +7 -4
- package/dist/docs/index.js +298 -298
- package/dist/docs/pack-topics.d.ts +18 -0
- package/dist/editor-completions/annotationValues.d.ts +1 -1
- package/dist/editor-completions/contextParser.d.ts +3 -2
- package/dist/editor-completions/dataTypes.d.ts +1 -1
- package/dist/editor-completions/goToDefinition.d.ts +1 -1
- package/dist/editor-completions/index.d.ts +8 -8
- package/dist/editor-completions/index.js +1244 -175
- package/dist/editor-completions/jsDocAnnotations.d.ts +1 -1
- package/dist/editor-completions/modifierCompletions.d.ts +1 -1
- package/dist/export/index.d.ts +7 -1
- package/dist/generated-branding.js +8 -21
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +7 -3
- package/dist/generator/annotation-generator.d.ts +84 -0
- package/dist/generator/async-detection.d.ts +1 -1
- package/dist/{body-generator.d.ts → generator/body-generator.d.ts} +7 -3
- package/dist/generator/code-utils.d.ts +15 -60
- package/dist/generator/connection-reads.d.ts +94 -0
- package/dist/generator/control-flow-plan.d.ts +74 -0
- package/dist/generator/control-flow.d.ts +1 -1
- package/dist/generator/exit-assembly.d.ts +11 -0
- package/dist/generator/index.d.ts +4 -5
- package/dist/generator/index.js +34 -11
- package/dist/generator/input-port-values.d.ts +22 -0
- package/dist/generator/{scope-function-generator.d.ts → node-arguments.d.ts} +76 -1
- package/dist/generator/node-invocation.d.ts +74 -0
- package/dist/{node-types-generator.d.ts → generator/node-types-generator.d.ts} +3 -2
- package/dist/generator/scope-closure.d.ts +23 -0
- package/dist/generator/scoped-child.d.ts +49 -0
- package/dist/{sugar-optimizer.d.ts → generator/sugar-optimizer.d.ts} +1 -1
- package/dist/generator/unified.d.ts +14 -2
- package/dist/generator/workflow-generator.d.ts +37 -0
- package/dist/index.d.ts +29 -26
- package/dist/index.js +1322 -92
- package/dist/jsdoc-port-sync/apply-diff.d.ts +15 -0
- package/dist/jsdoc-port-sync/callback-signature.d.ts +17 -0
- package/dist/jsdoc-port-sync/diff.d.ts +5 -7
- package/dist/jsdoc-port-sync/incomplete-lines.d.ts +41 -0
- package/dist/jsdoc-port-sync/index.d.ts +11 -11
- package/dist/jsdoc-port-sync/index.js +88 -45
- package/dist/jsdoc-port-sync/jsdoc-to-signature.d.ts +66 -0
- package/dist/jsdoc-port-sync/port-parser.d.ts +6 -24
- package/dist/jsdoc-port-sync/port-tag-placement.d.ts +55 -0
- package/dist/jsdoc-port-sync/scoped-ports.d.ts +21 -0
- package/dist/jsdoc-port-sync/signature-parser.d.ts +8 -1
- package/dist/jsdoc-port-sync/signature-to-jsdoc.d.ts +77 -0
- package/dist/jsdoc-port-sync/sync.d.ts +4 -1
- package/dist/marketplace/index.d.ts +5 -3
- package/dist/marketplace/index.js +62 -10
- package/dist/marketplace/install.d.ts +24 -0
- package/dist/marketplace/manifest.d.ts +1 -1
- package/dist/marketplace/npmrc.d.ts +24 -0
- package/dist/marketplace/registry.d.ts +46 -11
- package/dist/marketplace/types.d.ts +41 -24
- package/dist/marketplace/validator.d.ts +1 -1
- package/dist/mcp/debug-session.d.ts +2 -4
- package/dist/mcp/dist-imports.d.ts +12 -0
- package/dist/mcp/response-utils.d.ts +11 -9
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/tools-context.d.ts +11 -0
- package/dist/mcp/tools-diagram.d.ts +1 -2
- package/dist/mcp/tools-export.d.ts +2 -2
- package/dist/mcp/tools-marketplace.d.ts +1 -1
- package/dist/mcp/tools-run.d.ts +17 -0
- package/dist/mcp/tools-workflow.d.ts +3 -0
- package/dist/mcp/types.d.ts +4 -14
- package/dist/mcp/workflow-executor.d.ts +57 -19
- package/dist/npm-packages.d.ts +1 -1
- package/dist/npm-packages.js +10 -473
- package/dist/parser/annotation-parser.d.ts +88 -0
- package/dist/parser/annotation-suggestion.d.ts +23 -0
- package/dist/parser/durable-effect-contract.d.ts +10 -0
- package/dist/parser/expression-references.d.ts +101 -0
- package/dist/parser/external-node-types.d.ts +69 -0
- package/dist/{function-like.d.ts → parser/function-like.d.ts} +10 -3
- package/dist/parser/generated-sections.d.ts +25 -0
- package/dist/parser/import-resolution.d.ts +67 -0
- package/dist/parser/import-stub.d.ts +29 -0
- package/dist/parser/jsdoc/config-types.d.ts +209 -0
- package/dist/parser/jsdoc/graph-tags.d.ts +54 -0
- package/dist/parser/jsdoc/node-type-block.d.ts +23 -0
- package/dist/parser/jsdoc/parse-line-once.d.ts +11 -0
- package/dist/parser/jsdoc/port-tags.d.ts +42 -0
- package/dist/parser/jsdoc/runtime-tags.d.ts +65 -0
- package/dist/parser/jsdoc/signature-types.d.ts +52 -0
- package/dist/parser/jsdoc/workflow-block.d.ts +22 -0
- package/dist/parser/jsdoc-parser.d.ts +23 -0
- package/dist/parser/macro-expansion.d.ts +80 -0
- package/dist/parser/module-resolution.d.ts +38 -0
- package/dist/parser/node-inference.d.ts +39 -0
- package/dist/parser/npm-node-types.d.ts +24 -0
- package/dist/parser/pack-handlers.d.ts +12 -0
- package/dist/parser/path-data-resolution.d.ts +75 -0
- package/dist/parser/port-inference.d.ts +15 -0
- package/dist/{resolve-package-types.d.ts → parser/resolve-package-types.d.ts} +4 -0
- package/dist/parser/serialize-deploy-annotations.d.ts +19 -0
- package/dist/parser/tag-registry.d.ts +22 -0
- package/dist/parser/workflow-extraction.d.ts +39 -0
- package/dist/runtime/ExecutionContext.d.ts +87 -99
- package/dist/runtime/continuation-core.d.ts +147 -0
- package/dist/runtime/continuation.d.ts +48 -0
- package/dist/runtime/debug-controller.d.ts +7 -22
- package/dist/runtime/durable-execution.d.ts +176 -0
- package/dist/runtime/events.d.ts +16 -1
- package/dist/runtime/executable-module-contract.d.ts +16 -0
- package/dist/runtime/index.d.ts +13 -10
- package/dist/runtime/index.js +947 -9
- package/dist/runtime/strict-json.d.ts +15 -0
- package/dist/server/access.d.ts +12 -0
- package/dist/server/api.d.ts +126 -0
- package/dist/server/callback-delivery.d.ts +62 -0
- package/dist/server/callback-url.d.ts +44 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/http-error.d.ts +10 -0
- package/dist/server/index.d.ts +7 -1
- package/dist/server/index.js +125 -6
- package/dist/server/openapi-document.d.ts +33 -0
- package/dist/server/openapi.d.ts +32 -0
- package/dist/server/respond.d.ts +35 -0
- package/dist/server/route-params.d.ts +17 -0
- package/dist/server/route-plan.d.ts +41 -0
- package/dist/server/routes-catalog.d.ts +5 -0
- package/dist/server/routes-declared.d.ts +12 -0
- package/dist/server/routes-runs.d.ts +8 -0
- package/dist/server/run-driver.d.ts +58 -0
- package/dist/server/run-view.d.ts +26 -0
- package/dist/server/transport.d.ts +35 -0
- package/dist/server/types.d.ts +143 -52
- package/dist/server/webhook-server.d.ts +12 -38
- package/dist/server/workflow-registry.d.ts +7 -4
- package/dist/service-registry.d.ts +49 -0
- package/dist/service-registry.js +123 -0
- package/dist/testing/assertions.d.ts +2 -2
- package/dist/testing/index.d.ts +14 -13
- package/dist/testing/index.js +582 -24
- package/dist/testing/mock-approval.d.ts +3 -5
- package/dist/testing/mock-llm.d.ts +2 -3
- package/dist/testing/recorder.d.ts +5 -6
- package/dist/testing/replayer.d.ts +6 -7
- package/dist/testing/run-store-check.d.ts +3 -0
- package/dist/testing/token-tracker.d.ts +2 -2
- package/dist/{type-mappings.d.ts → types/type-mappings.d.ts} +13 -1
- package/dist/utils/error-utils.d.ts +4 -2
- package/dist/utils/port-ordering.d.ts +2 -2
- package/dist/utils/port-tag-utils.d.ts +1 -1
- package/dist/validation/agent-detection.d.ts +17 -5
- package/dist/validation/agent-rules.d.ts +6 -7
- package/dist/validation/core-rules.d.ts +49 -0
- package/dist/validation/design-rules.d.ts +9 -3
- package/dist/validation/rule-registry.d.ts +3 -3
- package/dist/validation/rules/connections.d.ts +27 -0
- package/dist/validation/rules/context.d.ts +11 -0
- package/dist/validation/rules/cycles.d.ts +17 -0
- package/dist/validation/rules/data-flow.d.ts +19 -0
- package/dist/validation/rules/scope-topology.d.ts +35 -0
- package/dist/validation/rules/type-compatibility.d.ts +30 -0
- package/dist/validation/type-checker.d.ts +39 -0
- package/dist/validation/validator-helpers.d.ts +31 -0
- package/dist/validation/validator.d.ts +30 -0
- package/docs/reference/advanced-annotations.md +277 -60
- package/docs/reference/built-in-nodes.md +164 -94
- package/docs/reference/cancellation.md +44 -0
- package/docs/reference/cli-reference.md +252 -234
- package/docs/reference/compilation.md +36 -109
- package/docs/reference/concepts.md +80 -63
- package/docs/reference/console.md +169 -0
- package/docs/reference/debugging.md +26 -57
- package/docs/reference/deployment.md +217 -143
- package/docs/reference/durable-gates.md +445 -0
- package/docs/reference/error-codes.md +90 -43
- package/docs/reference/export-interface.md +91 -26
- package/docs/reference/iterative-development.md +50 -28
- package/docs/reference/jsdoc-grammar.md +66 -311
- package/docs/reference/library.md +250 -0
- package/docs/reference/marketplace.md +301 -152
- package/docs/reference/mcp-tools.md +102 -0
- package/docs/reference/node-conversion.md +84 -59
- package/docs/reference/orientation.md +68 -0
- package/docs/reference/scaffold.md +5 -4
- package/docs/reference/tutorial.md +106 -166
- package/docs/reference/visual-reference.md +1 -1
- package/package.json +85 -50
- package/dist/agent/agent-loop.js +0 -156
- package/dist/agent/cli-spawn-config.js +0 -65
- package/dist/agent/device-connection.d.ts +0 -79
- package/dist/agent/device-connection.js +0 -184
- package/dist/agent/env-allowlist.js +0 -60
- package/dist/agent/providers/anthropic.js +0 -198
- package/dist/agent/providers/claude-cli.js +0 -161
- package/dist/agent/providers/openai-compat.js +0 -205
- package/dist/agent/providers/platform.d.ts +0 -23
- package/dist/agent/providers/platform.js +0 -132
- package/dist/agent/streaming.js +0 -186
- package/dist/agent/types.js +0 -20
- package/dist/annotation-generator.d.ts +0 -50
- package/dist/annotation-generator.js +0 -743
- package/dist/api/builder.js +0 -345
- package/dist/api/command-runner.js +0 -787
- package/dist/api/compile.js +0 -148
- package/dist/api/extract-types.js +0 -57
- package/dist/api/generate-in-place.js +0 -1477
- package/dist/api/generate.js +0 -514
- package/dist/api/helpers.js +0 -285
- package/dist/api/inline-runtime.js +0 -526
- package/dist/api/manipulation/connections.js +0 -151
- package/dist/api/manipulation/index.js +0 -41
- package/dist/api/manipulation/node-types.js +0 -200
- package/dist/api/manipulation/nodes.js +0 -355
- package/dist/api/manipulation/ports.js +0 -228
- package/dist/api/manipulation/scopes.js +0 -156
- package/dist/api/manipulation/workflow.js +0 -116
- package/dist/api/modify-operation.js +0 -177
- package/dist/api/parse.js +0 -115
- package/dist/api/patterns.d.ts +0 -112
- package/dist/api/patterns.js +0 -303
- package/dist/api/query.js +0 -840
- package/dist/api/templates.js +0 -117
- package/dist/api/transform.js +0 -40
- package/dist/api/validate.js +0 -64
- package/dist/api/validation-registry.js +0 -10
- package/dist/api/workflow-file-operations.js +0 -180
- package/dist/ast/builder.js +0 -395
- package/dist/ast/serialization-node.js +0 -30
- package/dist/ast/serialization.js +0 -134
- package/dist/ast/types.js +0 -2
- package/dist/ast/workflow-utils.js +0 -114
- package/dist/body-generator.js +0 -35
- package/dist/built-in-nodes/coercion-types.js +0 -61
- package/dist/built-in-nodes/delay.js +0 -29
- package/dist/built-in-nodes/generated-registry.js +0 -299
- package/dist/built-in-nodes/invoke-workflow.js +0 -36
- package/dist/built-in-nodes/mock-types.js +0 -42
- package/dist/built-in-nodes/wait-for-agent.js +0 -31
- package/dist/built-in-nodes/wait-for-event.js +0 -25
- package/dist/chevrotain-parser/coerce-parser.js +0 -103
- package/dist/chevrotain-parser/connect-parser.js +0 -136
- package/dist/chevrotain-parser/fan-parser.js +0 -149
- package/dist/chevrotain-parser/grammar-diagrams.js +0 -273
- package/dist/chevrotain-parser/index.js +0 -31
- package/dist/chevrotain-parser/map-parser.js +0 -130
- package/dist/chevrotain-parser/node-parser.js +0 -558
- package/dist/chevrotain-parser/path-parser.js +0 -140
- package/dist/chevrotain-parser/port-parser.js +0 -519
- package/dist/chevrotain-parser/position-parser.d.ts +0 -20
- package/dist/chevrotain-parser/position-parser.js +0 -83
- package/dist/chevrotain-parser/scope-parser.js +0 -104
- package/dist/chevrotain-parser/tokens.js +0 -429
- package/dist/chevrotain-parser/trigger-cancel-parser.js +0 -291
- package/dist/cli/commands/account.d.ts +0 -4
- package/dist/cli/commands/account.js +0 -50
- package/dist/cli/commands/ai-credentials.d.ts +0 -12
- package/dist/cli/commands/ai-credentials.js +0 -95
- package/dist/cli/commands/apikey.d.ts +0 -4
- package/dist/cli/commands/apikey.js +0 -64
- package/dist/cli/commands/auth.d.ts +0 -9
- package/dist/cli/commands/auth.js +0 -255
- package/dist/cli/commands/compile.js +0 -331
- package/dist/cli/commands/connect.d.ts +0 -4
- package/dist/cli/commands/connect.js +0 -163
- package/dist/cli/commands/context.js +0 -51
- package/dist/cli/commands/create.js +0 -139
- package/dist/cli/commands/deploy.d.ts +0 -6
- package/dist/cli/commands/deploy.js +0 -74
- package/dist/cli/commands/dev.js +0 -227
- package/dist/cli/commands/diagram.js +0 -39
- package/dist/cli/commands/diff.js +0 -57
- package/dist/cli/commands/docs.js +0 -74
- package/dist/cli/commands/export.js +0 -153
- package/dist/cli/commands/grammar.js +0 -40
- package/dist/cli/commands/implement.js +0 -91
- package/dist/cli/commands/init-personas.js +0 -541
- package/dist/cli/commands/init.js +0 -797
- package/dist/cli/commands/market.js +0 -440
- package/dist/cli/commands/mcp-setup.js +0 -433
- package/dist/cli/commands/migrate.js +0 -91
- package/dist/cli/commands/modify.js +0 -58
- package/dist/cli/commands/openapi.js +0 -71
- package/dist/cli/commands/org.d.ts +0 -8
- package/dist/cli/commands/org.js +0 -136
- package/dist/cli/commands/pattern.d.ts +0 -34
- package/dist/cli/commands/pattern.js +0 -186
- package/dist/cli/commands/plugin.d.ts +0 -16
- package/dist/cli/commands/plugin.js +0 -176
- package/dist/cli/commands/run.js +0 -742
- package/dist/cli/commands/serve.js +0 -93
- package/dist/cli/commands/status.js +0 -123
- package/dist/cli/commands/strip.js +0 -61
- package/dist/cli/commands/templates.js +0 -58
- package/dist/cli/commands/validate.js +0 -267
- package/dist/cli/commands/watch.js +0 -71
- package/dist/cli/config/credentials.d.ts +0 -15
- package/dist/cli/config/credentials.js +0 -43
- package/dist/cli/config/platform-client.d.ts +0 -123
- package/dist/cli/config/platform-client.js +0 -248
- package/dist/cli/env-setup.js +0 -12
- package/dist/cli/index.js +0 -850
- package/dist/cli/pack-commands.js +0 -122
- package/dist/cli/postinstall.d.ts +0 -16
- package/dist/cli/postinstall.js +0 -119
- package/dist/cli/templates/ai/mock-provider.js +0 -64
- package/dist/cli/templates/ai/types.js +0 -5
- package/dist/cli/templates/approvals/index.d.ts +0 -15
- package/dist/cli/templates/approvals/index.js +0 -241
- package/dist/cli/templates/index.js +0 -123
- package/dist/cli/templates/nodes/agent-router.js +0 -114
- package/dist/cli/templates/nodes/aggregator.js +0 -63
- package/dist/cli/templates/nodes/conversation-memory.js +0 -85
- package/dist/cli/templates/nodes/http.js +0 -80
- package/dist/cli/templates/nodes/human-approval.js +0 -109
- package/dist/cli/templates/nodes/json-extractor.js +0 -119
- package/dist/cli/templates/nodes/llm-call.js +0 -106
- package/dist/cli/templates/nodes/prompt-template.js +0 -52
- package/dist/cli/templates/nodes/rag-retriever.js +0 -128
- package/dist/cli/templates/nodes/tool-executor.js +0 -108
- package/dist/cli/templates/nodes/transformer.js +0 -68
- package/dist/cli/templates/nodes/validator.js +0 -62
- package/dist/cli/templates/pack-loader.js +0 -58
- package/dist/cli/templates/providers/index.js +0 -239
- package/dist/cli/templates/shared/approval-types.js +0 -31
- package/dist/cli/templates/shared/llm-types.js +0 -109
- package/dist/cli/templates/workflows/aggregator.js +0 -101
- package/dist/cli/templates/workflows/ai-agent.js +0 -326
- package/dist/cli/templates/workflows/ai-chat.js +0 -167
- package/dist/cli/templates/workflows/ai-rag.js +0 -184
- package/dist/cli/templates/workflows/ai-react.js +0 -293
- package/dist/cli/templates/workflows/conditional.js +0 -139
- package/dist/cli/templates/workflows/error-handler.js +0 -145
- package/dist/cli/templates/workflows/foreach.js +0 -140
- package/dist/cli/templates/workflows/sequential.js +0 -200
- package/dist/cli/templates/workflows/webhook.js +0 -158
- package/dist/cli/utils/cli-helpers.d.ts +0 -44
- package/dist/cli/utils/cli-helpers.js +0 -99
- package/dist/cli/utils/logger.js +0 -126
- package/dist/cli/utils/parse-int-strict.js +0 -17
- package/dist/cli/utils/safe-write.js +0 -54
- package/dist/deployment/config/defaults.d.ts +0 -29
- package/dist/deployment/config/defaults.js +0 -98
- package/dist/deployment/config/loader.d.ts +0 -24
- package/dist/deployment/config/loader.js +0 -237
- package/dist/deployment/config/types.d.ts +0 -117
- package/dist/deployment/config/types.js +0 -5
- package/dist/deployment/core/adapters.d.ts +0 -66
- package/dist/deployment/core/adapters.js +0 -156
- package/dist/deployment/core/executor.d.ts +0 -62
- package/dist/deployment/core/executor.js +0 -197
- package/dist/deployment/core/formatters.d.ts +0 -42
- package/dist/deployment/core/formatters.js +0 -146
- package/dist/deployment/openapi/generator.d.ts +0 -146
- package/dist/deployment/openapi/generator.js +0 -347
- package/dist/deployment/openapi/schema-converter.d.ts +0 -49
- package/dist/deployment/openapi/schema-converter.js +0 -192
- package/dist/deployment/targets/base.js +0 -855
- package/dist/deployment/types.d.ts +0 -140
- package/dist/deployment/types.js +0 -8
- package/dist/diagram/ascii-renderer.js +0 -528
- package/dist/diagram/geometry.js +0 -1226
- package/dist/diagram/html-viewer.d.ts +0 -21
- package/dist/diagram/html-viewer.js +0 -1745
- package/dist/diagram/layout.js +0 -143
- package/dist/diagram/orthogonal-router.js +0 -458
- package/dist/diagram/renderer.d.ts +0 -3
- package/dist/diagram/renderer.js +0 -280
- package/dist/diagram/theme.js +0 -282
- package/dist/diagram/types.js +0 -2
- package/dist/diff/WorkflowDiffer.js +0 -429
- package/dist/diff/formatDiff.js +0 -239
- package/dist/diff/impact.js +0 -119
- package/dist/diff/types.js +0 -35
- package/dist/doc-metadata/extractors/annotations.js +0 -638
- package/dist/doc-metadata/extractors/cli-commands.js +0 -326
- package/dist/doc-metadata/extractors/core-metadata.js +0 -61
- package/dist/doc-metadata/extractors/error-codes.js +0 -372
- package/dist/doc-metadata/extractors/grammar-rules.js +0 -51
- package/dist/doc-metadata/extractors/mcp-tools.js +0 -705
- package/dist/doc-metadata/extractors/plugin-api.d.ts +0 -19
- package/dist/doc-metadata/extractors/plugin-api.js +0 -279
- package/dist/doc-metadata/types.js +0 -5
- package/dist/editor-completions/annotationValues.js +0 -146
- package/dist/editor-completions/contextParser.js +0 -410
- package/dist/editor-completions/dataTypes.js +0 -95
- package/dist/editor-completions/goToDefinition.js +0 -112
- package/dist/editor-completions/jsDocAnnotations.js +0 -359
- package/dist/editor-completions/modifierCompletions.js +0 -197
- package/dist/editor-completions/types.js +0 -8
- package/dist/export/index.js +0 -212
- package/dist/extensions/index.d.ts +0 -16
- package/dist/extensions/index.js +0 -17
- package/dist/friendly-errors.js +0 -761
- package/dist/function-like.js +0 -84
- package/dist/generator/async-detection.js +0 -56
- package/dist/generator/code-utils.js +0 -551
- package/dist/generator/compile-target-registry.d.ts +0 -20
- package/dist/generator/compile-target-registry.js +0 -20
- package/dist/generator/control-flow.js +0 -358
- package/dist/generator/dev-mode-registry.d.ts +0 -27
- package/dist/generator/dev-mode-registry.js +0 -20
- package/dist/generator/scope-function-generator.js +0 -397
- package/dist/generator/unified.js +0 -1831
- package/dist/generator.d.ts +0 -54
- package/dist/generator.js +0 -101
- package/dist/jsdoc-parser.d.ts +0 -377
- package/dist/jsdoc-parser.js +0 -1189
- package/dist/jsdoc-port-sync/constants.js +0 -103
- package/dist/jsdoc-port-sync/diff.js +0 -319
- package/dist/jsdoc-port-sync/port-parser.js +0 -579
- package/dist/jsdoc-port-sync/rename.js +0 -256
- package/dist/jsdoc-port-sync/signature-parser.js +0 -559
- package/dist/jsdoc-port-sync/sync.js +0 -653
- package/dist/marketplace/manifest.js +0 -194
- package/dist/marketplace/registry.js +0 -198
- package/dist/marketplace/types.js +0 -9
- package/dist/marketplace/validator.js +0 -150
- package/dist/mcp/agent-channel.d.ts +0 -35
- package/dist/mcp/agent-channel.js +0 -61
- package/dist/mcp/auto-registration.d.ts +0 -3
- package/dist/mcp/auto-registration.js +0 -62
- package/dist/mcp/debug-session.js +0 -25
- package/dist/mcp/index.d.ts +0 -11
- package/dist/mcp/index.js +0 -10
- package/dist/mcp/pack-tools.js +0 -59
- package/dist/mcp/prompts.js +0 -68
- package/dist/mcp/response-utils.js +0 -97
- package/dist/mcp/run-registry.d.ts +0 -29
- package/dist/mcp/run-registry.js +0 -24
- package/dist/mcp/server.js +0 -63
- package/dist/mcp/tools-context.js +0 -51
- package/dist/mcp/tools-debug.js +0 -451
- package/dist/mcp/tools-diagram.js +0 -97
- package/dist/mcp/tools-docs.js +0 -62
- package/dist/mcp/tools-export.js +0 -222
- package/dist/mcp/tools-marketplace.js +0 -132
- package/dist/mcp/tools-model.d.ts +0 -3
- package/dist/mcp/tools-model.js +0 -279
- package/dist/mcp/tools-pattern.d.ts +0 -3
- package/dist/mcp/tools-pattern.js +0 -604
- package/dist/mcp/tools-query.js +0 -377
- package/dist/mcp/tools-resources.js +0 -41
- package/dist/mcp/tools-template.js +0 -119
- package/dist/mcp/tools-workflow-run.d.ts +0 -40
- package/dist/mcp/tools-workflow-run.js +0 -150
- package/dist/mcp/types.js +0 -2
- package/dist/mcp/workflow-executor.js +0 -242
- package/dist/migration/registry.js +0 -29
- package/dist/node-types-generator.js +0 -139
- package/dist/parser/tag-registry.js +0 -57
- package/dist/parser.d.ts +0 -232
- package/dist/parser.js +0 -2500
- package/dist/plugin/PluginPanel.d.ts +0 -12
- package/dist/plugin/PluginPanel.js +0 -5
- package/dist/plugin/index.d.ts +0 -13
- package/dist/plugin/index.js +0 -14
- package/dist/plugin/types.d.ts +0 -75
- package/dist/plugin/types.js +0 -8
- package/dist/resolve-package-types.js +0 -123
- package/dist/runtime/CancellationError.js +0 -20
- package/dist/runtime/ExecutionContext.js +0 -280
- package/dist/runtime/builtin-functions.js +0 -549
- package/dist/runtime/checkpoint.d.ts +0 -84
- package/dist/runtime/checkpoint.js +0 -225
- package/dist/runtime/debug-controller.js +0 -247
- package/dist/runtime/events.js +0 -2
- package/dist/runtime/function-registry.js +0 -66
- package/dist/runtime/parameter-resolver.js +0 -113
- package/dist/server/types.js +0 -5
- package/dist/server/webhook-server.js +0 -269
- package/dist/server/workflow-registry.js +0 -202
- package/dist/shared-project.js +0 -28
- package/dist/sugar-optimizer.js +0 -498
- package/dist/testing/assertions.js +0 -127
- package/dist/testing/mock-approval.js +0 -98
- package/dist/testing/mock-llm.js +0 -119
- package/dist/testing/recorder.js +0 -70
- package/dist/testing/replayer.js +0 -143
- package/dist/testing/token-tracker.js +0 -94
- package/dist/type-checker.d.ts +0 -42
- package/dist/type-checker.js +0 -190
- package/dist/type-mappings.js +0 -131
- package/dist/types/branded-ports.d.ts +0 -151
- package/dist/types/branded-ports.js +0 -121
- package/dist/types/index.d.ts +0 -5
- package/dist/types/index.js +0 -5
- package/dist/types.d.ts +0 -139
- package/dist/types.js +0 -15
- package/dist/utils/error-utils.js +0 -27
- package/dist/utils/lru-cache.js +0 -40
- package/dist/utils/port-ordering.js +0 -81
- package/dist/utils/port-tag-utils.js +0 -41
- package/dist/utils/string-distance.js +0 -56
- package/dist/validation/agent-detection.js +0 -115
- package/dist/validation/agent-rules.js +0 -262
- package/dist/validation/design-rules.js +0 -377
- package/dist/validation/rule-registry.js +0 -37
- package/dist/validator.d.ts +0 -113
- package/dist/validator.js +0 -1631
- package/docs/reference/patterns.md +0 -159
- package/scripts/postinstall.cjs +0 -86
- /package/dist/{shared-project.d.ts → parser/shared-project.d.ts} +0 -0
- /package/dist/{friendly-errors.d.ts → validation/friendly-errors.d.ts} +0 -0
|
@@ -0,0 +1,4256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COERCE_TYPE_MAP,
|
|
3
|
+
COERCION_NODE_TYPES,
|
|
4
|
+
collectModuleBindings,
|
|
5
|
+
expandExpressionReferences
|
|
6
|
+
} from "./chunk-4KDUEOFD.js";
|
|
7
|
+
import {
|
|
8
|
+
getErrorMessage
|
|
9
|
+
} from "./chunk-DUO6H5PR.js";
|
|
10
|
+
import {
|
|
11
|
+
assignImplicitPortOrders,
|
|
12
|
+
generateJSDocPortTag,
|
|
13
|
+
impliedPathDataEdgesInto,
|
|
14
|
+
tagHandlerRegistry
|
|
15
|
+
} from "./chunk-VP2P3BPA.js";
|
|
16
|
+
import {
|
|
17
|
+
extractFunctionLikes,
|
|
18
|
+
getPackageExports,
|
|
19
|
+
getSharedProject,
|
|
20
|
+
nestedAnnotationWarnings,
|
|
21
|
+
resolvePackageTypesPath
|
|
22
|
+
} from "./chunk-B6HQ6YS5.js";
|
|
23
|
+
import {
|
|
24
|
+
inferDataTypeFromTS,
|
|
25
|
+
isValidPortType,
|
|
26
|
+
stripOptionalUndefined
|
|
27
|
+
} from "./chunk-E5WN7IGO.js";
|
|
28
|
+
import {
|
|
29
|
+
parseCancelOnLine,
|
|
30
|
+
parseCoerceLine,
|
|
31
|
+
parseConnectLine,
|
|
32
|
+
parseFanInLine,
|
|
33
|
+
parseFanOutLine,
|
|
34
|
+
parseMapLine,
|
|
35
|
+
parseNodeLine,
|
|
36
|
+
parsePathLine,
|
|
37
|
+
parseRetriesLine,
|
|
38
|
+
parseScopeLine,
|
|
39
|
+
parseThrottleLine,
|
|
40
|
+
parseTimeoutLine,
|
|
41
|
+
parseTriggerLine
|
|
42
|
+
} from "./chunk-KMWCCOUJ.js";
|
|
43
|
+
import {
|
|
44
|
+
parsePortLine
|
|
45
|
+
} from "./chunk-QYPORSLO.js";
|
|
46
|
+
import {
|
|
47
|
+
EXECUTION_STRATEGIES,
|
|
48
|
+
KNOWN_NODETYPE_TAGS,
|
|
49
|
+
RESERVED_PORT_NAMES,
|
|
50
|
+
STANDARD_JSDOC_TAGS,
|
|
51
|
+
getKnownWorkflowTags,
|
|
52
|
+
isControlFlowPort,
|
|
53
|
+
isExecutePort,
|
|
54
|
+
isFailurePort,
|
|
55
|
+
isScopedMandatoryPort,
|
|
56
|
+
isSuccessPort
|
|
57
|
+
} from "./chunk-R4QGNK34.js";
|
|
58
|
+
|
|
59
|
+
// src/parser/generated-sections.ts
|
|
60
|
+
var MARKERS = {
|
|
61
|
+
RUNTIME_START: "// @flow-weaver-runtime-start",
|
|
62
|
+
RUNTIME_END: "// @flow-weaver-runtime-end",
|
|
63
|
+
BODY_START: "// @flow-weaver-body-start",
|
|
64
|
+
BODY_END: "// @flow-weaver-body-end",
|
|
65
|
+
IMPORTS_START: "// @flow-weaver-imports-start",
|
|
66
|
+
IMPORTS_END: "// @flow-weaver-imports-end"
|
|
67
|
+
};
|
|
68
|
+
function hasInPlaceMarkers(source) {
|
|
69
|
+
return source.includes(MARKERS.RUNTIME_START) && source.includes(MARKERS.RUNTIME_END) && source.includes(MARKERS.BODY_START) && source.includes(MARKERS.BODY_END);
|
|
70
|
+
}
|
|
71
|
+
function stripGeneratedSections(source) {
|
|
72
|
+
let result = source;
|
|
73
|
+
const runtimeStartIdx = result.indexOf(MARKERS.RUNTIME_START);
|
|
74
|
+
const runtimeEndIdx = result.indexOf(MARKERS.RUNTIME_END);
|
|
75
|
+
if (runtimeStartIdx !== -1 && runtimeEndIdx !== -1) {
|
|
76
|
+
const lineStart = result.lastIndexOf("\n", runtimeStartIdx);
|
|
77
|
+
const lineEnd = result.indexOf("\n", runtimeEndIdx + MARKERS.RUNTIME_END.length);
|
|
78
|
+
result = result.slice(0, lineStart === -1 ? 0 : lineStart) + result.slice(lineEnd === -1 ? result.length : lineEnd);
|
|
79
|
+
}
|
|
80
|
+
while (true) {
|
|
81
|
+
const bodyStartIdx = result.indexOf(MARKERS.BODY_START);
|
|
82
|
+
const bodyEndIdx = result.indexOf(MARKERS.BODY_END);
|
|
83
|
+
if (bodyStartIdx === -1 || bodyEndIdx === -1 || bodyEndIdx < bodyStartIdx) break;
|
|
84
|
+
const before = result.slice(0, bodyStartIdx);
|
|
85
|
+
const after = result.slice(bodyEndIdx + MARKERS.BODY_END.length);
|
|
86
|
+
result = before + `throw new Error('Not implemented');` + after;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/parser/npm-node-types.ts
|
|
92
|
+
function resolveNpmNodeTypes(ast, workdir) {
|
|
93
|
+
if (!ast.nodeTypes || ast.nodeTypes.length === 0) {
|
|
94
|
+
return ast;
|
|
95
|
+
}
|
|
96
|
+
const resolvedNodeTypes = ast.nodeTypes.map((nodeType) => {
|
|
97
|
+
if (!nodeType.importSource) {
|
|
98
|
+
return nodeType;
|
|
99
|
+
}
|
|
100
|
+
const packageExports = getPackageExports(nodeType.importSource, workdir);
|
|
101
|
+
const matchingExport = packageExports.find(
|
|
102
|
+
(exp) => exp.name === nodeType.name || exp.function === nodeType.functionName
|
|
103
|
+
);
|
|
104
|
+
if (!matchingExport) {
|
|
105
|
+
return nodeType;
|
|
106
|
+
}
|
|
107
|
+
const inputs = {};
|
|
108
|
+
const outputs = {};
|
|
109
|
+
for (const port of matchingExport.ports) {
|
|
110
|
+
if (port.direction === "INPUT") {
|
|
111
|
+
inputs[port.name] = {
|
|
112
|
+
dataType: port.type,
|
|
113
|
+
label: port.defaultLabel
|
|
114
|
+
};
|
|
115
|
+
} else if (port.direction === "OUTPUT") {
|
|
116
|
+
outputs[port.name] = {
|
|
117
|
+
dataType: port.type,
|
|
118
|
+
label: port.defaultLabel
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
...nodeType,
|
|
124
|
+
inputs,
|
|
125
|
+
outputs,
|
|
126
|
+
isAsync: matchingExport.synchronicity === "ASYNC"
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
...ast,
|
|
131
|
+
nodeTypes: resolvedNodeTypes
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/parser/annotation-parser.ts
|
|
136
|
+
import { createHash } from "node:crypto";
|
|
137
|
+
import * as fs3 from "node:fs";
|
|
138
|
+
|
|
139
|
+
// src/utils/lru-cache.ts
|
|
140
|
+
var LRUCache = class {
|
|
141
|
+
map = /* @__PURE__ */ new Map();
|
|
142
|
+
maxSize;
|
|
143
|
+
constructor(maxSize) {
|
|
144
|
+
this.maxSize = maxSize;
|
|
145
|
+
}
|
|
146
|
+
get(key) {
|
|
147
|
+
const value = this.map.get(key);
|
|
148
|
+
if (value !== void 0) {
|
|
149
|
+
this.map.delete(key);
|
|
150
|
+
this.map.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
has(key) {
|
|
155
|
+
return this.map.has(key);
|
|
156
|
+
}
|
|
157
|
+
set(key, value) {
|
|
158
|
+
if (this.map.has(key)) {
|
|
159
|
+
this.map.delete(key);
|
|
160
|
+
}
|
|
161
|
+
this.map.set(key, value);
|
|
162
|
+
while (this.map.size > this.maxSize) {
|
|
163
|
+
const firstKey = this.map.keys().next().value;
|
|
164
|
+
this.map.delete(firstKey);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
clear() {
|
|
168
|
+
this.map.clear();
|
|
169
|
+
}
|
|
170
|
+
get size() {
|
|
171
|
+
return this.map.size;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/utils/string-distance.ts
|
|
176
|
+
function levenshteinDistance(a, b) {
|
|
177
|
+
const la = a.length;
|
|
178
|
+
const lb = b.length;
|
|
179
|
+
if (la === 0) return lb;
|
|
180
|
+
if (lb === 0) return la;
|
|
181
|
+
let prev = Array.from({ length: lb + 1 }, (_, i) => i);
|
|
182
|
+
let curr = new Array(lb + 1);
|
|
183
|
+
for (let i = 1; i <= la; i++) {
|
|
184
|
+
curr[0] = i;
|
|
185
|
+
for (let j = 1; j <= lb; j++) {
|
|
186
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
187
|
+
curr[j] = Math.min(
|
|
188
|
+
curr[j - 1] + 1,
|
|
189
|
+
// insertion
|
|
190
|
+
prev[j] + 1,
|
|
191
|
+
// deletion
|
|
192
|
+
prev[j - 1] + cost
|
|
193
|
+
// substitution
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
[prev, curr] = [curr, prev];
|
|
197
|
+
}
|
|
198
|
+
return prev[lb];
|
|
199
|
+
}
|
|
200
|
+
function adaptiveMaxDistance(target) {
|
|
201
|
+
const len = target.length;
|
|
202
|
+
if (len <= 3) return 1;
|
|
203
|
+
if (len <= 6) return 2;
|
|
204
|
+
return 3;
|
|
205
|
+
}
|
|
206
|
+
function findClosestMatches(target, candidates, maxDistance) {
|
|
207
|
+
const effectiveMax = maxDistance ?? adaptiveMaxDistance(target);
|
|
208
|
+
const scored = candidates.filter((c) => c !== target).map((c) => ({ name: c, distance: levenshteinDistance(target, c) })).filter((s) => s.distance <= effectiveMax).sort((a, b) => a.distance - b.distance);
|
|
209
|
+
return scored.map((s) => s.name);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/parser/jsdoc/signature-types.ts
|
|
213
|
+
function unwrapPromise(type) {
|
|
214
|
+
if (type.getText().startsWith("Promise<")) {
|
|
215
|
+
const typeArgs = type.getTypeArguments();
|
|
216
|
+
if (typeArgs.length > 0) {
|
|
217
|
+
return typeArgs[0];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return type;
|
|
221
|
+
}
|
|
222
|
+
function resolveCallSignatures(callbackType) {
|
|
223
|
+
let sigs = callbackType.getCallSignatures();
|
|
224
|
+
if (sigs.length > 0) return sigs;
|
|
225
|
+
try {
|
|
226
|
+
sigs = callbackType.getApparentType().getCallSignatures();
|
|
227
|
+
if (sigs.length > 0) return sigs;
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const symbol = callbackType.getSymbol() ?? callbackType.getAliasSymbol();
|
|
232
|
+
const decl = symbol?.getDeclarations()?.[0];
|
|
233
|
+
if (decl) {
|
|
234
|
+
sigs = decl.getType().getCallSignatures();
|
|
235
|
+
if (sigs.length > 0) return sigs;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
return sigs;
|
|
240
|
+
}
|
|
241
|
+
function extractCallbackReturnFieldType(callbackType, fieldName) {
|
|
242
|
+
const callSignatures = resolveCallSignatures(callbackType);
|
|
243
|
+
if (callSignatures.length === 0) {
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
const returnType = unwrapPromise(callSignatures[0].getReturnType());
|
|
247
|
+
const property = returnType.getProperty(fieldName);
|
|
248
|
+
if (!property) {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
const propertyType = getPropertyType(property, returnType);
|
|
252
|
+
if (!propertyType) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
return propertyType.getText(void 0, 0);
|
|
256
|
+
}
|
|
257
|
+
function getPropertyType(property, containerType) {
|
|
258
|
+
const valueDecl = property.getValueDeclaration();
|
|
259
|
+
if (valueDecl) {
|
|
260
|
+
return valueDecl.getType();
|
|
261
|
+
}
|
|
262
|
+
const declaredType = containerType.getPropertyOrThrow(property.getName());
|
|
263
|
+
if (declaredType) {
|
|
264
|
+
const decls = declaredType.getDeclarations();
|
|
265
|
+
if (decls.length > 0) {
|
|
266
|
+
return decls[0].getType();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
function extractCallbackParamType(callbackType, paramName) {
|
|
272
|
+
const callSignatures = resolveCallSignatures(callbackType);
|
|
273
|
+
if (callSignatures.length === 0) {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
const parameters = callSignatures[0].getParameters();
|
|
277
|
+
for (const param of parameters) {
|
|
278
|
+
if (param.getName() === paramName) {
|
|
279
|
+
const valueDecl = param.getValueDeclaration();
|
|
280
|
+
if (valueDecl) {
|
|
281
|
+
const paramType = valueDecl.getType();
|
|
282
|
+
return paramType.getText(void 0, 0);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
function typeScopedPort(direction, name, scope, func, warnings) {
|
|
289
|
+
const nodeTypeName = func.getName() || "unknown";
|
|
290
|
+
const scopeParam = func.getParameters().find((p) => p.getName() === scope);
|
|
291
|
+
if (!scopeParam) {
|
|
292
|
+
const callbackShape = direction === "INPUT" ? `(...) => { ${name}: YourType }` : `(${name}: YourType, ...) => { ... }`;
|
|
293
|
+
warnings.push(
|
|
294
|
+
`Scoped ${direction} port '${name}' references scope '${scope}', but no callback parameter named '${scope}' was found in node type '${nodeTypeName}'. Add a callback parameter: ${scope}: ${callbackShape}`
|
|
295
|
+
);
|
|
296
|
+
return { type: "ANY" };
|
|
297
|
+
}
|
|
298
|
+
const extract = direction === "INPUT" ? extractCallbackReturnFieldType : extractCallbackParamType;
|
|
299
|
+
const tsType = extract(scopeParam.getType(), name);
|
|
300
|
+
if (tsType) return { type: inferDataTypeFromTS(tsType), tsType };
|
|
301
|
+
const expectation = direction === "INPUT" ? `should have a return type that includes '${name}'` : `should have a parameter named '${name}'`;
|
|
302
|
+
warnings.push(
|
|
303
|
+
`Cannot infer type for scoped ${direction} port '${name}' in scope '${scope}' of node type '${nodeTypeName}'. The callback parameter '${scope}' ${expectation}. Consider adding an explicit type annotation to the callback signature.`
|
|
304
|
+
);
|
|
305
|
+
return { type: "ANY" };
|
|
306
|
+
}
|
|
307
|
+
function applyDeclaredType(direction, name, declared, inferred, tsType, func, warnings) {
|
|
308
|
+
if (!declared) return inferred;
|
|
309
|
+
const nodeTypeName = func.getName() || "unknown";
|
|
310
|
+
if (!isValidPortType(declared)) {
|
|
311
|
+
warnings.push(
|
|
312
|
+
`@${direction} ${name} in node type '${nodeTypeName}' declares [type:${declared}], which is not a port type. Use one of STRING, NUMBER, BOOLEAN, ARRAY, OBJECT, FUNCTION, ANY or STEP.`
|
|
313
|
+
);
|
|
314
|
+
return inferred;
|
|
315
|
+
}
|
|
316
|
+
if (inferred === "ANY") return declared;
|
|
317
|
+
if (declared !== inferred && declared !== "ANY") {
|
|
318
|
+
warnings.push(
|
|
319
|
+
`@${direction} ${name} in node type '${nodeTypeName}' declares [type:${declared}] but the signature gives ${inferred}${tsType ? ` (${tsType})` : ""}. The signature type is used; change one to match.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return inferred;
|
|
323
|
+
}
|
|
324
|
+
function objectFieldPattern(name) {
|
|
325
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
326
|
+
return new RegExp(`(?:^|[{;,\\s])${escaped}\\??\\s*:\\s*([^;},]+)`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/parser/jsdoc/port-tags.ts
|
|
330
|
+
function workflowParameterDefault(tagText, name) {
|
|
331
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
332
|
+
const match = tagText.match(new RegExp(`\\[\\s*${escaped}\\s*=\\s*([^\\]\\r\\n]*)\\]`, "u"));
|
|
333
|
+
return match?.[1]?.trim();
|
|
334
|
+
}
|
|
335
|
+
function parseDefaultValue(value) {
|
|
336
|
+
try {
|
|
337
|
+
return JSON.parse(value);
|
|
338
|
+
} catch {
|
|
339
|
+
return value;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function parseInputTag(tag, config, func, warnings) {
|
|
343
|
+
const comment = tag.getCommentText() || "";
|
|
344
|
+
const result = parsePortLine(`@input ${comment}`, warnings);
|
|
345
|
+
if (!result) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const { name, defaultValue, isOptional, scope, order, mergeStrategy, hidden, description, customMetadata } = result;
|
|
349
|
+
let type;
|
|
350
|
+
let tsType;
|
|
351
|
+
const isScopedStepInput = scope && isScopedMandatoryPort(name);
|
|
352
|
+
if (isExecutePort(name) || isScopedStepInput) {
|
|
353
|
+
if (result.dataType && result.dataType !== "STEP") {
|
|
354
|
+
warnings.push(`Port "${name}" is a reserved control port. Its type will always be STEP.`);
|
|
355
|
+
}
|
|
356
|
+
type = "STEP";
|
|
357
|
+
} else if (scope) {
|
|
358
|
+
({ type, tsType } = typeScopedPort("INPUT", name, scope, func, warnings));
|
|
359
|
+
} else {
|
|
360
|
+
const param = func.getParameters().find((p) => {
|
|
361
|
+
const pName = p.getName();
|
|
362
|
+
return pName === name || pName === `_${name}`;
|
|
363
|
+
});
|
|
364
|
+
if (param) {
|
|
365
|
+
const rawTsType = param.getType().getText(param);
|
|
366
|
+
tsType = param.isOptional() || param.hasInitializer() ? stripOptionalUndefined(rawTsType) : rawTsType;
|
|
367
|
+
type = inferDataTypeFromTS(tsType);
|
|
368
|
+
} else {
|
|
369
|
+
type = "ANY";
|
|
370
|
+
}
|
|
371
|
+
type = applyDeclaredType("input", name, result.dataType, type, tsType, func, warnings);
|
|
372
|
+
}
|
|
373
|
+
let label = description?.trim();
|
|
374
|
+
let expression = void 0;
|
|
375
|
+
if (label && label.startsWith("Expression:")) {
|
|
376
|
+
expression = label.substring("Expression:".length).trim();
|
|
377
|
+
label = void 0;
|
|
378
|
+
}
|
|
379
|
+
if (Object.prototype.hasOwnProperty.call(config.inputs, name)) {
|
|
380
|
+
warnings.push(`Duplicate @input "${name}". The second declaration will overwrite the first.`);
|
|
381
|
+
}
|
|
382
|
+
config.inputs[name] = {
|
|
383
|
+
type,
|
|
384
|
+
defaultValue: defaultValue === void 0 ? void 0 : parseDefaultValue(defaultValue),
|
|
385
|
+
...isOptional && { optional: true },
|
|
386
|
+
label,
|
|
387
|
+
...expression && { expression },
|
|
388
|
+
...scope && { scope },
|
|
389
|
+
...mergeStrategy && { mergeStrategy },
|
|
390
|
+
...hidden && { hidden },
|
|
391
|
+
...(order !== void 0 || customMetadata) && {
|
|
392
|
+
metadata: { ...order !== void 0 && { order }, ...customMetadata }
|
|
393
|
+
},
|
|
394
|
+
...tsType && { tsType }
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function parseOutputTag(tag, config, func, warnings) {
|
|
398
|
+
const comment = tag.getCommentText() || "";
|
|
399
|
+
const result = parsePortLine(`@output ${comment}`, warnings);
|
|
400
|
+
if (!result) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const { name, scope, order, hidden, description, customMetadata } = result;
|
|
404
|
+
let type;
|
|
405
|
+
let tsType;
|
|
406
|
+
const isScopedStepOutput = scope && isScopedMandatoryPort(name);
|
|
407
|
+
if (isSuccessPort(name) || isFailurePort(name) || isScopedStepOutput) {
|
|
408
|
+
if (result.dataType && result.dataType !== "STEP") {
|
|
409
|
+
warnings.push(`Port "${name}" is a reserved control port. Its type will always be STEP.`);
|
|
410
|
+
}
|
|
411
|
+
type = "STEP";
|
|
412
|
+
} else if (scope) {
|
|
413
|
+
({ type, tsType } = typeScopedPort("OUTPUT", name, scope, func, warnings));
|
|
414
|
+
} else {
|
|
415
|
+
const returnType = unwrapPromise(func.getReturnType());
|
|
416
|
+
const property = returnType.getProperty(name);
|
|
417
|
+
if (property) {
|
|
418
|
+
const propertyType = getPropertyType(property, returnType);
|
|
419
|
+
if (propertyType) {
|
|
420
|
+
tsType = propertyType.getText(void 0, 0);
|
|
421
|
+
type = inferDataTypeFromTS(tsType);
|
|
422
|
+
} else {
|
|
423
|
+
type = "ANY";
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
type = "ANY";
|
|
427
|
+
}
|
|
428
|
+
type = applyDeclaredType("output", name, result.dataType, type, tsType, func, warnings);
|
|
429
|
+
}
|
|
430
|
+
if (Object.prototype.hasOwnProperty.call(config.outputs, name)) {
|
|
431
|
+
warnings.push(`Duplicate @output "${name}". The second declaration will overwrite the first.`);
|
|
432
|
+
}
|
|
433
|
+
config.outputs[name] = {
|
|
434
|
+
type,
|
|
435
|
+
label: description?.trim(),
|
|
436
|
+
...scope && { scope },
|
|
437
|
+
...hidden && { hidden },
|
|
438
|
+
...(order !== void 0 || customMetadata) && {
|
|
439
|
+
metadata: { ...order !== void 0 && { order }, ...customMetadata }
|
|
440
|
+
},
|
|
441
|
+
...tsType && { tsType }
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function parseStepTag(tag, config, func, warnings) {
|
|
445
|
+
const comment = tag.getCommentText() || "";
|
|
446
|
+
const result = parsePortLine(`@step ${comment}`, warnings);
|
|
447
|
+
if (!result) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const { name, description } = result;
|
|
451
|
+
const param = func.getParameters().find((p) => p.getName() === name);
|
|
452
|
+
if (param) {
|
|
453
|
+
config.inputs[name] = {
|
|
454
|
+
type: "STEP",
|
|
455
|
+
label: description?.trim()
|
|
456
|
+
};
|
|
457
|
+
} else {
|
|
458
|
+
config.outputs[name] = {
|
|
459
|
+
type: "STEP",
|
|
460
|
+
label: description?.trim()
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function parseReturnTag(tag, config, func, warnings) {
|
|
465
|
+
const comment = tag.getCommentText() || "";
|
|
466
|
+
const result = parsePortLine(`@output ${comment}`, warnings);
|
|
467
|
+
if (!result) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const { name, order, description, customMetadata } = result;
|
|
471
|
+
let type = "ANY";
|
|
472
|
+
if (isSuccessPort(name) || isFailurePort(name)) {
|
|
473
|
+
type = "STEP";
|
|
474
|
+
} else if (func) {
|
|
475
|
+
const returnType = func.getReturnType();
|
|
476
|
+
const returnTypeText = returnType.getText();
|
|
477
|
+
const fieldMatch = returnTypeText.match(objectFieldPattern(name));
|
|
478
|
+
if (fieldMatch) {
|
|
479
|
+
type = inferDataTypeFromTS(fieldMatch[1].trim());
|
|
480
|
+
} else {
|
|
481
|
+
warnings.push(`Could not infer type for @returns "${name}", defaulting to ANY.`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
config.returnPorts = config.returnPorts || {};
|
|
485
|
+
if (Object.prototype.hasOwnProperty.call(config.returnPorts, name)) {
|
|
486
|
+
warnings.push(`Duplicate @returns "${name}". The second declaration will overwrite the first.`);
|
|
487
|
+
}
|
|
488
|
+
config.returnPorts[name] = {
|
|
489
|
+
dataType: type,
|
|
490
|
+
label: description?.trim(),
|
|
491
|
+
...(order !== void 0 || customMetadata) && {
|
|
492
|
+
metadata: { ...order !== void 0 && { order }, ...customMetadata }
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function parseParamTag(tag, config, func, warnings) {
|
|
497
|
+
const compilerNode = tag.compilerNode;
|
|
498
|
+
const tagWithGetName = tag;
|
|
499
|
+
const name = compilerNode.name?.getText?.() || tagWithGetName.getName && tagWithGetName.getName();
|
|
500
|
+
if (!name) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const comment = tag.getCommentText() || "";
|
|
504
|
+
const result = parsePortLine(`@input ${name} ${comment}`, warnings);
|
|
505
|
+
const order = result?.order;
|
|
506
|
+
const description = result?.description;
|
|
507
|
+
const optional = compilerNode.isBracketed === true;
|
|
508
|
+
const defaultSource = workflowParameterDefault(tag.getText(), name);
|
|
509
|
+
let type = "ANY";
|
|
510
|
+
if (isExecutePort(name)) {
|
|
511
|
+
type = "STEP";
|
|
512
|
+
} else if (func) {
|
|
513
|
+
const params = func.getParameters();
|
|
514
|
+
const paramsParam = params.find((p) => p.getName() === "params");
|
|
515
|
+
if (paramsParam) {
|
|
516
|
+
const paramType = paramsParam.getType();
|
|
517
|
+
const paramTypeText = paramType.getText();
|
|
518
|
+
const isCatchAllRecord = /^Record<string,\s*(never|any|unknown)>$/.test(paramTypeText) || paramTypeText === "{}" || /^\{\s*\[[\w]+:\s*string\]:\s*(never|any|unknown);\s*\}$/.test(paramTypeText);
|
|
519
|
+
if (!isCatchAllRecord) {
|
|
520
|
+
const fieldMatch = paramTypeText.match(objectFieldPattern(name));
|
|
521
|
+
if (fieldMatch) {
|
|
522
|
+
type = inferDataTypeFromTS(fieldMatch[1].trim());
|
|
523
|
+
} else {
|
|
524
|
+
warnings.push(`@param "${name}" does not match any field in the params object. Type defaults to ANY.`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
config.startPorts = config.startPorts || {};
|
|
530
|
+
if (Object.prototype.hasOwnProperty.call(config.startPorts, name)) {
|
|
531
|
+
warnings.push(`Duplicate @param "${name}". The second declaration will overwrite the first.`);
|
|
532
|
+
}
|
|
533
|
+
config.startPorts[name] = {
|
|
534
|
+
dataType: type,
|
|
535
|
+
label: description?.trim(),
|
|
536
|
+
...optional && { optional: true },
|
|
537
|
+
...defaultSource === void 0 ? {} : { default: parseDefaultValue(defaultSource) },
|
|
538
|
+
...order !== void 0 && { metadata: { order } }
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/parser/jsdoc/parse-line-once.ts
|
|
543
|
+
function parseLineOnce(parse, line, warnings, fallback) {
|
|
544
|
+
const before = warnings.length;
|
|
545
|
+
const result = parse(line, warnings);
|
|
546
|
+
if (result === null && warnings.length === before) warnings.push(fallback);
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/parser/jsdoc/runtime-tags.ts
|
|
551
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
552
|
+
function parseTriggerTag(tag, config, warnings, tagRegistry) {
|
|
553
|
+
const comment = (tag.getCommentText() || "").trim();
|
|
554
|
+
const result = parseTriggerLine(`@trigger ${comment}`, warnings);
|
|
555
|
+
if (result) {
|
|
556
|
+
config.trigger = config.trigger || {};
|
|
557
|
+
if (result.event) config.trigger.event = result.event;
|
|
558
|
+
if (result.cron) config.trigger.cron = result.cron;
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (tagRegistry && tagRegistry.has("_trigger")) {
|
|
562
|
+
if (!config.deploy) config.deploy = {};
|
|
563
|
+
tagRegistry.handle("_trigger", comment, "workflow", config.deploy, warnings);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
warnings.push(`Invalid @trigger format: @trigger ${comment}`);
|
|
567
|
+
}
|
|
568
|
+
function parseCancelOnTag(tag, config, warnings) {
|
|
569
|
+
const comment = tag.getCommentText() || "";
|
|
570
|
+
const result = parseLineOnce(parseCancelOnLine, `@cancelOn ${comment}`, warnings, `Invalid @cancelOn format: @cancelOn ${comment}`);
|
|
571
|
+
if (!result) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
config.cancelOn = result;
|
|
575
|
+
}
|
|
576
|
+
function parseThrottleTag(tag, config, warnings) {
|
|
577
|
+
const comment = tag.getCommentText() || "";
|
|
578
|
+
const result = parseLineOnce(parseThrottleLine, `@throttle ${comment}`, warnings, `Invalid @throttle format: @throttle ${comment}`);
|
|
579
|
+
if (!result) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
config.throttle = result;
|
|
583
|
+
}
|
|
584
|
+
function parseRetriesTag(tag, config, warnings) {
|
|
585
|
+
const comment = tag.getCommentText() || "";
|
|
586
|
+
const result = parseRetriesLine(`@retries ${comment.trim()}`, warnings);
|
|
587
|
+
if (result && result.retries >= 0) config.retries = result.retries;
|
|
588
|
+
}
|
|
589
|
+
function parseTimeoutTag(tag, config, warnings) {
|
|
590
|
+
const comment = tag.getCommentText() || "";
|
|
591
|
+
const raw = comment.trim();
|
|
592
|
+
const quoted = /^"(?:[^"\\]|\\.)*"$/.test(raw) ? raw : `"${raw.replace(/^['"]|['"]$/g, "")}"`;
|
|
593
|
+
const result = parseTimeoutLine(`@timeout ${quoted}`, warnings);
|
|
594
|
+
if (result && result.timeout) config.timeout = result.timeout;
|
|
595
|
+
}
|
|
596
|
+
function parseHttpTag(tag, config, warnings) {
|
|
597
|
+
const text = (tag.getCommentText() || "").trim();
|
|
598
|
+
const parts = text.split(/\s+/).filter(Boolean);
|
|
599
|
+
const fail = (why) => {
|
|
600
|
+
warnings.push(`Invalid @http: "${text}". ${why} Format: @http METHOD /path [mode=sync|async] [auth=bearer|none] [callback]`);
|
|
601
|
+
};
|
|
602
|
+
if (parts.length < 2) return fail("Give a method and a path.");
|
|
603
|
+
const method = parts[0].toUpperCase();
|
|
604
|
+
if (!HTTP_METHODS.has(method)) return fail(`"${parts[0]}" is not one of GET, POST, PUT, PATCH, DELETE.`);
|
|
605
|
+
const routePath = parts[1];
|
|
606
|
+
if (!/^\/(?:[A-Za-z0-9_\-.~%]+|:[A-Za-z_][A-Za-z0-9_]*)(?:\/(?:[A-Za-z0-9_\-.~%]+|:[A-Za-z_][A-Za-z0-9_]*))*\/?$|^\/$/.test(routePath)) {
|
|
607
|
+
return fail(`"${routePath}" is not a path: it starts with / and its segments are words or :params.`);
|
|
608
|
+
}
|
|
609
|
+
const route = { method, path: routePath.length > 1 ? routePath.replace(/\/$/, "") : routePath };
|
|
610
|
+
for (const opt of parts.slice(2)) {
|
|
611
|
+
const [key, raw] = opt.includes("=") ? opt.split("=", 2) : [opt, void 0];
|
|
612
|
+
const value = raw?.replace(/^["']|["']$/g, "");
|
|
613
|
+
if (key === "mode" && (value === "sync" || value === "async")) {
|
|
614
|
+
if (value === "async") route.mode = "async";
|
|
615
|
+
} else if (key === "auth" && (value === "bearer" || value === "none")) {
|
|
616
|
+
if (value === "none") route.auth = "none";
|
|
617
|
+
} else if (key === "callback" && (value === void 0 || value === "true" || value === "false")) {
|
|
618
|
+
if (value !== "false") route.callback = true;
|
|
619
|
+
} else return fail(`"${opt}" is not an option.`);
|
|
620
|
+
}
|
|
621
|
+
config.http = config.http || [];
|
|
622
|
+
if (config.http.some((r) => r.method === route.method && r.path === route.path)) {
|
|
623
|
+
warnings.push(`Duplicate @http route ${route.method} ${route.path}. The first one stands.`);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
config.http.push(route);
|
|
627
|
+
}
|
|
628
|
+
function parseDeployTag(tag, deployMap) {
|
|
629
|
+
const text = (tag.getCommentText() || "").trim();
|
|
630
|
+
if (!text) return;
|
|
631
|
+
const spaceIdx = text.indexOf(" ");
|
|
632
|
+
const targetName = spaceIdx === -1 ? text : text.substring(0, spaceIdx);
|
|
633
|
+
const rest = spaceIdx === -1 ? "" : text.substring(spaceIdx + 1);
|
|
634
|
+
if (!targetName) return;
|
|
635
|
+
if (!deployMap[targetName]) deployMap[targetName] = {};
|
|
636
|
+
if (!rest) return;
|
|
637
|
+
const kvRegex = /(\w[\w-]*)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([\S]+))/g;
|
|
638
|
+
let match;
|
|
639
|
+
while ((match = kvRegex.exec(rest)) !== null) {
|
|
640
|
+
const key = match[1];
|
|
641
|
+
const quotedVal = match[2];
|
|
642
|
+
const bareVal = match[3];
|
|
643
|
+
if (quotedVal !== void 0) {
|
|
644
|
+
if (quotedVal.includes(",")) {
|
|
645
|
+
deployMap[targetName][key] = quotedVal.split(",").map((v) => v.trim());
|
|
646
|
+
} else {
|
|
647
|
+
deployMap[targetName][key] = quotedVal;
|
|
648
|
+
}
|
|
649
|
+
} else if (bareVal !== void 0) {
|
|
650
|
+
if (bareVal === "true") {
|
|
651
|
+
deployMap[targetName][key] = true;
|
|
652
|
+
} else if (bareVal === "false") {
|
|
653
|
+
deployMap[targetName][key] = false;
|
|
654
|
+
} else {
|
|
655
|
+
const num = Number(bareVal);
|
|
656
|
+
deployMap[targetName][key] = isNaN(num) ? bareVal : num;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/parser/jsdoc/node-type-block.ts
|
|
663
|
+
function parseResilience(comment, config, warnings) {
|
|
664
|
+
const resilience = {};
|
|
665
|
+
const attributes = comment.matchAll(/(\w+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g);
|
|
666
|
+
for (const match of attributes) {
|
|
667
|
+
const key = match[1];
|
|
668
|
+
const value = match[2] ?? match[3] ?? match[4];
|
|
669
|
+
if (key === "retries") {
|
|
670
|
+
const retries = Number(value);
|
|
671
|
+
if (Number.isInteger(retries) && retries > 0) {
|
|
672
|
+
resilience.retries = retries;
|
|
673
|
+
} else {
|
|
674
|
+
warnings.push("@resilience retries must be a positive integer.");
|
|
675
|
+
}
|
|
676
|
+
} else if (key === "fallback") {
|
|
677
|
+
if (value.trim()) resilience.fallback = value.trim();
|
|
678
|
+
} else {
|
|
679
|
+
warnings.push(`Unknown @resilience option "${key}". Supported options: retries, fallback.`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (resilience.retries !== void 0 || resilience.fallback !== void 0) {
|
|
683
|
+
config.resilience = resilience;
|
|
684
|
+
} else {
|
|
685
|
+
warnings.push('@resilience requires retries=N and/or fallback="provider".');
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function parseNodeType(func, warnings, tagRegistry) {
|
|
689
|
+
const jsdocs = func.getJsDocs();
|
|
690
|
+
if (jsdocs.length === 0) return null;
|
|
691
|
+
let jsdoc = null;
|
|
692
|
+
let flowWeaverTag = null;
|
|
693
|
+
let isNodeShorthand = false;
|
|
694
|
+
for (const doc of jsdocs) {
|
|
695
|
+
const tags2 = doc.getTags();
|
|
696
|
+
const tag = tags2.find(
|
|
697
|
+
(t) => {
|
|
698
|
+
if (t.getTagName() !== "flowWeaver") return false;
|
|
699
|
+
const comment = t.getCommentText()?.trim();
|
|
700
|
+
return comment === "nodeType" || comment === "node";
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
if (tag) {
|
|
704
|
+
jsdoc = doc;
|
|
705
|
+
flowWeaverTag = tag;
|
|
706
|
+
isNodeShorthand = flowWeaverTag.getCommentText()?.trim() === "node";
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (!jsdoc || !flowWeaverTag) return null;
|
|
711
|
+
const tags = jsdoc.getTags();
|
|
712
|
+
const config = {
|
|
713
|
+
inputs: {},
|
|
714
|
+
outputs: {}
|
|
715
|
+
};
|
|
716
|
+
if (isNodeShorthand) {
|
|
717
|
+
config.expression = true;
|
|
718
|
+
}
|
|
719
|
+
const descriptionText = jsdoc.getDescription();
|
|
720
|
+
if (descriptionText && descriptionText.trim()) {
|
|
721
|
+
config.description = descriptionText.trim();
|
|
722
|
+
}
|
|
723
|
+
tags.forEach((tag) => {
|
|
724
|
+
const tagName = tag.getTagName();
|
|
725
|
+
const comment = tag.getCommentText() || "";
|
|
726
|
+
switch (tagName) {
|
|
727
|
+
case "name":
|
|
728
|
+
config.name = comment.trim();
|
|
729
|
+
break;
|
|
730
|
+
case "label":
|
|
731
|
+
config.label = comment.trim();
|
|
732
|
+
break;
|
|
733
|
+
case "description":
|
|
734
|
+
config.description = comment.trim();
|
|
735
|
+
break;
|
|
736
|
+
case "color":
|
|
737
|
+
config.color = comment.trim().replace(/^["']|["']$/g, "");
|
|
738
|
+
break;
|
|
739
|
+
case "icon":
|
|
740
|
+
config.icon = comment.trim().replace(/^["']|["']$/g, "");
|
|
741
|
+
break;
|
|
742
|
+
case "tag": {
|
|
743
|
+
config.tags = config.tags || [];
|
|
744
|
+
const tagMatch = comment.match(/^(\S+)(?:\s+"([^"]+)")?$/);
|
|
745
|
+
if (tagMatch) {
|
|
746
|
+
config.tags.push({
|
|
747
|
+
label: tagMatch[1],
|
|
748
|
+
...tagMatch[2] && { tooltip: tagMatch[2] }
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
case "executeWhen":
|
|
754
|
+
config.executeWhen = comment.trim();
|
|
755
|
+
break;
|
|
756
|
+
case "scope":
|
|
757
|
+
config.scope = comment.trim();
|
|
758
|
+
break;
|
|
759
|
+
case "expression":
|
|
760
|
+
config.expression = true;
|
|
761
|
+
break;
|
|
762
|
+
case "pullExecution": {
|
|
763
|
+
const pullValue = comment.trim();
|
|
764
|
+
if (pullValue) {
|
|
765
|
+
config.defaultConfig = config.defaultConfig || {};
|
|
766
|
+
config.defaultConfig.pullExecution = { triggerPort: pullValue };
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
case "resilience":
|
|
771
|
+
parseResilience(comment, config, warnings);
|
|
772
|
+
break;
|
|
773
|
+
case "input":
|
|
774
|
+
parseInputTag(tag, config, func, warnings);
|
|
775
|
+
break;
|
|
776
|
+
case "output":
|
|
777
|
+
parseOutputTag(tag, config, func, warnings);
|
|
778
|
+
break;
|
|
779
|
+
case "step":
|
|
780
|
+
parseStepTag(tag, config, func, warnings);
|
|
781
|
+
break;
|
|
782
|
+
case "deploy":
|
|
783
|
+
config.deploy = config.deploy || {};
|
|
784
|
+
parseDeployTag(tag, config.deploy);
|
|
785
|
+
break;
|
|
786
|
+
default: {
|
|
787
|
+
if (tagName === "param" || tagName === "returns" || tagName === "return") {
|
|
788
|
+
warnings.push(`@${tagName} is for workflows, not node types. Use @input/@output instead.`);
|
|
789
|
+
} else if (tagRegistry && tagRegistry.has(tagName)) {
|
|
790
|
+
if (!config.deploy) config.deploy = {};
|
|
791
|
+
tagRegistry.handle(tagName, comment, "nodeType", config.deploy, warnings);
|
|
792
|
+
} else if (!KNOWN_NODETYPE_TAGS.has(tagName) && !STANDARD_JSDOC_TAGS.has(tagName)) {
|
|
793
|
+
const suggestions = findClosestMatches(tagName, [...KNOWN_NODETYPE_TAGS]);
|
|
794
|
+
const hint = suggestions.length > 0 ? ` Did you mean @${suggestions[0]}?` : "";
|
|
795
|
+
warnings.push(`Unknown annotation @${tagName} in nodeType block.${hint}`);
|
|
796
|
+
}
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
return config;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/parser/jsdoc/graph-tags.ts
|
|
805
|
+
var POSITION_ATTR = /\s*\[position:\s*-?\d+\s+-?\d+\]|,\s*position:\s*-?\d+\s+-?\d+(?=\s*[,\]])|(?<=\[)\s*position:\s*-?\d+\s+-?\d+\s*,\s*/g;
|
|
806
|
+
var positionGone = (where) => `${where}: node positions are no longer part of the grammar and this was ignored. Remove it, or run \`fw compile\` / \`fw migrate\` to rewrite the block without it.`;
|
|
807
|
+
function parseImportTag(tag, config, warnings) {
|
|
808
|
+
const comment = tag.getCommentText()?.trim() || "";
|
|
809
|
+
const match = comment.match(/^(\S+)\s+(\S+)\s+from\s+["']([^"']+)["']$/);
|
|
810
|
+
if (match) {
|
|
811
|
+
const [, name, functionName, importSource] = match;
|
|
812
|
+
config.imports.push({ name, functionName, importSource });
|
|
813
|
+
} else {
|
|
814
|
+
warnings.push(
|
|
815
|
+
`Invalid @fwImport tag format: "${comment}". Expected: @fwImport nodeName functionName from "packageName"`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function parseNodeTag(tag, config, warnings) {
|
|
820
|
+
let comment = tag.getCommentText() || "";
|
|
821
|
+
if (POSITION_ATTR.test(comment)) {
|
|
822
|
+
POSITION_ATTR.lastIndex = 0;
|
|
823
|
+
comment = comment.replace(POSITION_ATTR, "");
|
|
824
|
+
warnings.push(positionGone(`@node ${comment.trim().split(/\s+/)[0]} [position:]`));
|
|
825
|
+
}
|
|
826
|
+
POSITION_ATTR.lastIndex = 0;
|
|
827
|
+
const result = parseNodeLine(`@node ${comment}`, warnings);
|
|
828
|
+
if (!result) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const {
|
|
832
|
+
instanceId,
|
|
833
|
+
nodeType,
|
|
834
|
+
parentScope,
|
|
835
|
+
label,
|
|
836
|
+
expressions,
|
|
837
|
+
portOrder,
|
|
838
|
+
portLabel,
|
|
839
|
+
minimized,
|
|
840
|
+
pullExecution,
|
|
841
|
+
size,
|
|
842
|
+
color,
|
|
843
|
+
icon,
|
|
844
|
+
tags,
|
|
845
|
+
attributes,
|
|
846
|
+
suppress
|
|
847
|
+
} = result;
|
|
848
|
+
const line = tag.getStartLineNumber();
|
|
849
|
+
let portConfigs;
|
|
850
|
+
if (portOrder) {
|
|
851
|
+
portConfigs = Object.entries(portOrder).map(([portName, order]) => ({
|
|
852
|
+
portName,
|
|
853
|
+
order
|
|
854
|
+
}));
|
|
855
|
+
}
|
|
856
|
+
if (portLabel) {
|
|
857
|
+
portConfigs = portConfigs || [];
|
|
858
|
+
for (const [portName, labelVal] of Object.entries(portLabel)) {
|
|
859
|
+
const existingIndex = portConfigs.findIndex((pc) => pc.portName === portName);
|
|
860
|
+
if (existingIndex >= 0) {
|
|
861
|
+
portConfigs[existingIndex] = { ...portConfigs[existingIndex], label: labelVal };
|
|
862
|
+
} else {
|
|
863
|
+
portConfigs.push({ portName, label: labelVal });
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (expressions) {
|
|
868
|
+
portConfigs = portConfigs || [];
|
|
869
|
+
for (const [portName, expression] of Object.entries(expressions)) {
|
|
870
|
+
const existingIndex = portConfigs.findIndex((pc) => pc.portName === portName);
|
|
871
|
+
if (existingIndex >= 0) {
|
|
872
|
+
portConfigs[existingIndex] = { ...portConfigs[existingIndex], expression };
|
|
873
|
+
} else {
|
|
874
|
+
portConfigs.push({ portName, expression });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
config.instances.push({
|
|
879
|
+
id: instanceId,
|
|
880
|
+
type: nodeType,
|
|
881
|
+
...parentScope && { parentScope },
|
|
882
|
+
...label && { label },
|
|
883
|
+
...portConfigs && portConfigs.length > 0 && { portConfigs },
|
|
884
|
+
...pullExecution && { pullExecution: { triggerPort: pullExecution } },
|
|
885
|
+
...minimized && { minimized },
|
|
886
|
+
...color && { color },
|
|
887
|
+
...icon && { icon },
|
|
888
|
+
...tags && tags.length > 0 && { tags },
|
|
889
|
+
...size && { width: size.width, height: size.height },
|
|
890
|
+
...attributes && Object.keys(attributes).length > 0 && { attributes },
|
|
891
|
+
...suppress && suppress.length > 0 && { suppressWarnings: suppress },
|
|
892
|
+
sourceLocation: { line, column: 0 }
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
function parseConnectTag(tag, config, warnings) {
|
|
896
|
+
const comment = tag.getCommentText() || "";
|
|
897
|
+
const result = parseLineOnce(parseConnectLine, `@connect ${comment}`, warnings, `Invalid @connect tag format: @connect ${comment}`);
|
|
898
|
+
if (!result) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const { source, target, coerce } = result;
|
|
902
|
+
const line = tag.getStartLineNumber();
|
|
903
|
+
config.connections.push({
|
|
904
|
+
from: {
|
|
905
|
+
node: source.nodeId,
|
|
906
|
+
port: source.portName,
|
|
907
|
+
...source.scope && { scope: source.scope }
|
|
908
|
+
},
|
|
909
|
+
to: {
|
|
910
|
+
node: target.nodeId,
|
|
911
|
+
port: target.portName,
|
|
912
|
+
...target.scope && { scope: target.scope }
|
|
913
|
+
},
|
|
914
|
+
sourceLocation: { line, column: 0 },
|
|
915
|
+
...coerce && { coerce }
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function parseScopeTag(tag, config, warnings) {
|
|
919
|
+
const comment = tag.getCommentText() || "";
|
|
920
|
+
const result = parseLineOnce(parseScopeLine, `@scope ${comment}`, warnings, `Invalid @scope tag format: ${comment}`);
|
|
921
|
+
if (!result) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
config.scopes[result.scopeName] = result.children;
|
|
925
|
+
}
|
|
926
|
+
function parseMapTag(tag, config, warnings) {
|
|
927
|
+
const comment = tag.getCommentText() || "";
|
|
928
|
+
const result = parseLineOnce(parseMapLine, `@map ${comment}`, warnings, `Invalid @map tag format: ${comment}`);
|
|
929
|
+
if (!result) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
config.maps = config.maps || [];
|
|
933
|
+
config.maps.push({
|
|
934
|
+
instanceId: result.instanceId,
|
|
935
|
+
childId: result.childId,
|
|
936
|
+
sourceNode: result.sourceNode,
|
|
937
|
+
sourcePort: result.sourcePort,
|
|
938
|
+
...result.inputPort && { inputPort: result.inputPort },
|
|
939
|
+
...result.outputPort && { outputPort: result.outputPort }
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
function parsePathTag(tag, config, warnings) {
|
|
943
|
+
const comment = tag.getCommentText() || "";
|
|
944
|
+
const results = parseLineOnce(parsePathLine, `@path ${comment}`, warnings, `Invalid @path tag format: ${comment}`);
|
|
945
|
+
if (!results) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
config.paths = config.paths || [];
|
|
949
|
+
for (const result of results) {
|
|
950
|
+
config.paths.push({
|
|
951
|
+
steps: result.steps
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function parseFanOutTag(tag, config, warnings) {
|
|
956
|
+
const comment = tag.getCommentText() || "";
|
|
957
|
+
const result = parseLineOnce(parseFanOutLine, `@fanOut ${comment}`, warnings, `Invalid @fanOut tag format: ${comment}`);
|
|
958
|
+
if (!result) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (!result.source.port) {
|
|
962
|
+
warnings.push(`@fanOut source must specify a port: ${comment}`);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
config.fanOuts = config.fanOuts || [];
|
|
966
|
+
config.fanOuts.push({
|
|
967
|
+
source: { node: result.source.node, port: result.source.port },
|
|
968
|
+
targets: result.targets
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
function parseFanInTag(tag, config, warnings) {
|
|
972
|
+
const comment = tag.getCommentText() || "";
|
|
973
|
+
const result = parseLineOnce(parseFanInLine, `@fanIn ${comment}`, warnings, `Invalid @fanIn tag format: ${comment}`);
|
|
974
|
+
if (!result) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (!result.target.port) {
|
|
978
|
+
warnings.push(`@fanIn target must specify a port: ${comment}`);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
config.fanIns = config.fanIns || [];
|
|
982
|
+
config.fanIns.push({
|
|
983
|
+
sources: result.sources,
|
|
984
|
+
target: { node: result.target.node, port: result.target.port }
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function parseCoerceTag(tag, config, warnings) {
|
|
988
|
+
const comment = tag.getCommentText() || "";
|
|
989
|
+
const result = parseLineOnce(parseCoerceLine, `@coerce ${comment}`, warnings, `Invalid @coerce tag format: ${comment}`);
|
|
990
|
+
if (!result) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
config.coercions = config.coercions || [];
|
|
994
|
+
config.coercions.push({
|
|
995
|
+
instanceId: result.instanceId,
|
|
996
|
+
source: result.source,
|
|
997
|
+
target: result.target,
|
|
998
|
+
targetType: result.targetType
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/parser/jsdoc/workflow-block.ts
|
|
1003
|
+
function parseWorkflow(func, warnings, tagRegistry) {
|
|
1004
|
+
const jsdocs = func.getJsDocs();
|
|
1005
|
+
if (jsdocs.length === 0) return null;
|
|
1006
|
+
let jsdoc = null;
|
|
1007
|
+
let flowWeaverTag = null;
|
|
1008
|
+
for (const doc of jsdocs) {
|
|
1009
|
+
const tags2 = doc.getTags();
|
|
1010
|
+
const tag = tags2.find(
|
|
1011
|
+
(t) => t.getTagName() === "flowWeaver" && t.getCommentText()?.trim() === "workflow"
|
|
1012
|
+
);
|
|
1013
|
+
if (tag) {
|
|
1014
|
+
jsdoc = doc;
|
|
1015
|
+
flowWeaverTag = tag;
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (!jsdoc || !flowWeaverTag) return null;
|
|
1020
|
+
const tags = jsdoc.getTags();
|
|
1021
|
+
const config = {
|
|
1022
|
+
imports: [],
|
|
1023
|
+
instances: [],
|
|
1024
|
+
connections: [],
|
|
1025
|
+
scopes: {}
|
|
1026
|
+
};
|
|
1027
|
+
const descriptionText = jsdoc.getDescription();
|
|
1028
|
+
if (descriptionText && descriptionText.trim()) {
|
|
1029
|
+
config.description = descriptionText.trim();
|
|
1030
|
+
}
|
|
1031
|
+
tags.forEach((tag) => {
|
|
1032
|
+
const tagName = tag.getTagName();
|
|
1033
|
+
const comment = tag.getCommentText() || "";
|
|
1034
|
+
switch (tagName) {
|
|
1035
|
+
case "name":
|
|
1036
|
+
config.name = comment.trim();
|
|
1037
|
+
break;
|
|
1038
|
+
case "fwImport":
|
|
1039
|
+
parseImportTag(tag, config, warnings);
|
|
1040
|
+
break;
|
|
1041
|
+
case "description":
|
|
1042
|
+
config.description = comment.trim();
|
|
1043
|
+
break;
|
|
1044
|
+
case "strictTypes":
|
|
1045
|
+
config.strictTypes = comment.trim().toLowerCase() !== "false";
|
|
1046
|
+
break;
|
|
1047
|
+
case "autoConnect":
|
|
1048
|
+
config.autoConnect = true;
|
|
1049
|
+
break;
|
|
1050
|
+
case "node":
|
|
1051
|
+
parseNodeTag(tag, config, warnings);
|
|
1052
|
+
break;
|
|
1053
|
+
case "position":
|
|
1054
|
+
warnings.push(positionGone(`@position ${comment.trim()}`));
|
|
1055
|
+
break;
|
|
1056
|
+
case "connect":
|
|
1057
|
+
parseConnectTag(tag, config, warnings);
|
|
1058
|
+
break;
|
|
1059
|
+
case "scope":
|
|
1060
|
+
parseScopeTag(tag, config, warnings);
|
|
1061
|
+
break;
|
|
1062
|
+
case "map":
|
|
1063
|
+
parseMapTag(tag, config, warnings);
|
|
1064
|
+
break;
|
|
1065
|
+
case "path":
|
|
1066
|
+
parsePathTag(tag, config, warnings);
|
|
1067
|
+
break;
|
|
1068
|
+
case "fanOut":
|
|
1069
|
+
parseFanOutTag(tag, config, warnings);
|
|
1070
|
+
break;
|
|
1071
|
+
case "fanIn":
|
|
1072
|
+
parseFanInTag(tag, config, warnings);
|
|
1073
|
+
break;
|
|
1074
|
+
case "coerce":
|
|
1075
|
+
parseCoerceTag(tag, config, warnings);
|
|
1076
|
+
break;
|
|
1077
|
+
case "trigger":
|
|
1078
|
+
parseTriggerTag(tag, config, warnings, tagRegistry);
|
|
1079
|
+
break;
|
|
1080
|
+
case "http":
|
|
1081
|
+
parseHttpTag(tag, config, warnings);
|
|
1082
|
+
break;
|
|
1083
|
+
case "cancelOn":
|
|
1084
|
+
parseCancelOnTag(tag, config, warnings);
|
|
1085
|
+
break;
|
|
1086
|
+
case "retries":
|
|
1087
|
+
parseRetriesTag(tag, config, warnings);
|
|
1088
|
+
break;
|
|
1089
|
+
case "timeout":
|
|
1090
|
+
parseTimeoutTag(tag, config, warnings);
|
|
1091
|
+
break;
|
|
1092
|
+
case "throttle":
|
|
1093
|
+
parseThrottleTag(tag, config, warnings);
|
|
1094
|
+
break;
|
|
1095
|
+
case "deploy":
|
|
1096
|
+
config.deploy = config.deploy || {};
|
|
1097
|
+
parseDeployTag(tag, config.deploy);
|
|
1098
|
+
break;
|
|
1099
|
+
case "param":
|
|
1100
|
+
parseParamTag(tag, config, func, warnings);
|
|
1101
|
+
break;
|
|
1102
|
+
case "return":
|
|
1103
|
+
case "returns":
|
|
1104
|
+
parseReturnTag(tag, config, func, warnings);
|
|
1105
|
+
break;
|
|
1106
|
+
default: {
|
|
1107
|
+
if (tagName === "color" || tagName === "icon" || tagName === "tag") {
|
|
1108
|
+
warnings.push(`@${tagName} is for node types, not workflows. Use it on @flowWeaver nodeType instead.`);
|
|
1109
|
+
} else if (tagName === "input" || tagName === "output" || tagName === "step") {
|
|
1110
|
+
warnings.push(`@${tagName} is for node types, not workflows. Use @param/@returns for workflows.`);
|
|
1111
|
+
} else if (tagRegistry && tagRegistry.has(tagName)) {
|
|
1112
|
+
if (!config.deploy) config.deploy = {};
|
|
1113
|
+
tagRegistry.handle(tagName, comment, "workflow", config.deploy, warnings);
|
|
1114
|
+
} else {
|
|
1115
|
+
const knownTags = getKnownWorkflowTags(tagRegistry?.getRegisteredTags());
|
|
1116
|
+
if (!knownTags.has(tagName) && !STANDARD_JSDOC_TAGS.has(tagName)) {
|
|
1117
|
+
const suggestions = findClosestMatches(tagName, [...knownTags]);
|
|
1118
|
+
const hint = suggestions.length > 0 ? ` Did you mean @${suggestions[0]}?` : "";
|
|
1119
|
+
warnings.push(`Unknown annotation @${tagName} in workflow block.${hint}`);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
return config;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/parser/jsdoc-parser.ts
|
|
1130
|
+
var jsdocParser = {
|
|
1131
|
+
parseNodeType,
|
|
1132
|
+
parseWorkflow
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// src/built-in-nodes/generated-registry.ts
|
|
1136
|
+
var BUILT_IN_NODE_TYPES = [
|
|
1137
|
+
{
|
|
1138
|
+
type: "NodeType",
|
|
1139
|
+
name: "delay",
|
|
1140
|
+
functionName: "delay",
|
|
1141
|
+
isAsync: true,
|
|
1142
|
+
receivesAbortSignal: true,
|
|
1143
|
+
receivesRuntime: true,
|
|
1144
|
+
durablePure: true,
|
|
1145
|
+
hasSuccessPort: true,
|
|
1146
|
+
hasFailurePort: true,
|
|
1147
|
+
executeWhen: "CONJUNCTION",
|
|
1148
|
+
variant: "FUNCTION",
|
|
1149
|
+
inputs: {
|
|
1150
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
1151
|
+
duration: { dataType: "STRING", label: 'Duration to sleep (e.g. "30s", "5m", "1h", "2d")', tsType: "string" }
|
|
1152
|
+
},
|
|
1153
|
+
outputs: {
|
|
1154
|
+
elapsed: { dataType: "BOOLEAN", label: "Always true after sleep completes", tsType: "boolean" },
|
|
1155
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
1156
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true }
|
|
1157
|
+
},
|
|
1158
|
+
helperText: `
|
|
1159
|
+
function __fw_getMockConfig(runtime?: NodeExecutionRuntime): FwMockConfig | undefined {
|
|
1160
|
+
return runtime?.runtime.services.mocks;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function __fw_lookupMock<T>(
|
|
1164
|
+
section: Record<string, T> | undefined,
|
|
1165
|
+
key: string,
|
|
1166
|
+
runtime?: NodeExecutionRuntime,
|
|
1167
|
+
): T | undefined {
|
|
1168
|
+
if (!section) return undefined;
|
|
1169
|
+
|
|
1170
|
+
const nodeId = runtime?.nodeId;
|
|
1171
|
+
if (nodeId) {
|
|
1172
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
1173
|
+
if (qualified !== undefined) return qualified;
|
|
1174
|
+
const any = section[\`\${nodeId}:*\`];
|
|
1175
|
+
if (any !== undefined) return any;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return section[key];
|
|
1179
|
+
}
|
|
1180
|
+
`.trim(),
|
|
1181
|
+
helperTextProduction: void 0,
|
|
1182
|
+
functionText: `
|
|
1183
|
+
async function delay(
|
|
1184
|
+
execute: boolean,
|
|
1185
|
+
duration: string,
|
|
1186
|
+
abortSignal?: AbortSignal,
|
|
1187
|
+
runtime?: NodeExecutionRuntime,
|
|
1188
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; elapsed: boolean }> {
|
|
1189
|
+
if (!execute) return { onSuccess: false, onFailure: false, elapsed: false };
|
|
1190
|
+
|
|
1191
|
+
const mocks = __fw_getMockConfig(runtime);
|
|
1192
|
+
if (mocks?.fast) {
|
|
1193
|
+
// Fast mode: skip real sleep, keep async behavior with 1ms
|
|
1194
|
+
await __fw_waitForDuration(1, abortSignal);
|
|
1195
|
+
} else {
|
|
1196
|
+
const ms = __fw_parseDuration(duration) ?? 0;
|
|
1197
|
+
// setTimeout holds a signed 32-bit delay. Past 2^31-1 ms (24.8 days) it
|
|
1198
|
+
// fires at once with a warning instead of waiting, so a longer duration
|
|
1199
|
+
// is refused rather than silently cut short. A run that waits that long
|
|
1200
|
+
// wants \`sleep\`, which holds no process.
|
|
1201
|
+
if (ms > 2147483647) {
|
|
1202
|
+
throw new Error(
|
|
1203
|
+
'delay: "' + duration + '" is longer than setTimeout can wait (24.8 days); use sleep for a wait this long',
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
await __fw_waitForDuration(ms, abortSignal);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function __fw_waitForDuration(ms: number, abortSignal?: AbortSignal): Promise<void> {
|
|
1213
|
+
if (abortSignal?.aborted) return Promise.reject(new CancellationError());
|
|
1214
|
+
|
|
1215
|
+
return new Promise((resolve, reject) => {
|
|
1216
|
+
let settled = false;
|
|
1217
|
+
const onAbort = () => {
|
|
1218
|
+
if (settled) return;
|
|
1219
|
+
settled = true;
|
|
1220
|
+
clearTimeout(timer);
|
|
1221
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
1222
|
+
reject(new CancellationError());
|
|
1223
|
+
};
|
|
1224
|
+
const timer = setTimeout(() => {
|
|
1225
|
+
if (settled) return;
|
|
1226
|
+
settled = true;
|
|
1227
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
1228
|
+
resolve();
|
|
1229
|
+
}, ms);
|
|
1230
|
+
|
|
1231
|
+
abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
1232
|
+
if (abortSignal?.aborted) onAbort();
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// \`<number><unit>\` with unit \`ms\`, \`s\`, \`m\`, \`h\` or \`d\`, whitespace around
|
|
1237
|
+
// either allowed; \`undefined\` for anything else, including an empty string.
|
|
1238
|
+
// This is the one reading of a duration string in the package: the
|
|
1239
|
+
// coordinator's clock (\`src/coordinator/time.ts\`) re-exports it for a
|
|
1240
|
+
// \`sleep\` duration and a gate \`timeout\`, and this file's body is inlined into
|
|
1241
|
+
// compiled workflows, which is why the function lives here and not there.
|
|
1242
|
+
function __fw_parseDuration(text: unknown): number | undefined {
|
|
1243
|
+
if (typeof text !== 'string') return undefined;
|
|
1244
|
+
const match = /^\\s*(\\d+)\\s*(ms|s|m|h|d)\\s*$/.exec(text);
|
|
1245
|
+
if (!match) return undefined;
|
|
1246
|
+
const units: Record<string, number> = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
1247
|
+
return Number(match[1]) * units[match[2]];
|
|
1248
|
+
}
|
|
1249
|
+
`.trim(),
|
|
1250
|
+
functionTextProduction: `
|
|
1251
|
+
async function delay(
|
|
1252
|
+
execute: boolean,
|
|
1253
|
+
duration: string,
|
|
1254
|
+
abortSignal?: AbortSignal,
|
|
1255
|
+
runtime?: NodeExecutionRuntime,
|
|
1256
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; elapsed: boolean }> {
|
|
1257
|
+
if (!execute) return { onSuccess: false, onFailure: false, elapsed: false };
|
|
1258
|
+
|
|
1259
|
+
const ms = __fw_parseDuration(duration) ?? 0;
|
|
1260
|
+
// setTimeout holds a signed 32-bit delay. Past 2^31-1 ms (24.8 days) it
|
|
1261
|
+
// fires at once with a warning instead of waiting, so a longer duration
|
|
1262
|
+
// is refused rather than silently cut short. A run that waits that long
|
|
1263
|
+
// wants \`sleep\`, which holds no process.
|
|
1264
|
+
if (ms > 2147483647) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
'delay: "' + duration + '" is longer than setTimeout can wait (24.8 days); use sleep for a wait this long',
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
await __fw_waitForDuration(ms, abortSignal);
|
|
1270
|
+
|
|
1271
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function __fw_waitForDuration(ms: number, abortSignal?: AbortSignal): Promise<void> {
|
|
1275
|
+
if (abortSignal?.aborted) return Promise.reject(new CancellationError());
|
|
1276
|
+
|
|
1277
|
+
return new Promise((resolve, reject) => {
|
|
1278
|
+
let settled = false;
|
|
1279
|
+
const onAbort = () => {
|
|
1280
|
+
if (settled) return;
|
|
1281
|
+
settled = true;
|
|
1282
|
+
clearTimeout(timer);
|
|
1283
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
1284
|
+
reject(new CancellationError());
|
|
1285
|
+
};
|
|
1286
|
+
const timer = setTimeout(() => {
|
|
1287
|
+
if (settled) return;
|
|
1288
|
+
settled = true;
|
|
1289
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
1290
|
+
resolve();
|
|
1291
|
+
}, ms);
|
|
1292
|
+
|
|
1293
|
+
abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
1294
|
+
if (abortSignal?.aborted) onAbort();
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// \`<number><unit>\` with unit \`ms\`, \`s\`, \`m\`, \`h\` or \`d\`, whitespace around
|
|
1299
|
+
// either allowed; \`undefined\` for anything else, including an empty string.
|
|
1300
|
+
// This is the one reading of a duration string in the package: the
|
|
1301
|
+
// coordinator's clock (\`src/coordinator/time.ts\`) re-exports it for a
|
|
1302
|
+
// \`sleep\` duration and a gate \`timeout\`, and this file's body is inlined into
|
|
1303
|
+
// compiled workflows, which is why the function lives here and not there.
|
|
1304
|
+
function __fw_parseDuration(text: unknown): number | undefined {
|
|
1305
|
+
if (typeof text !== 'string') return undefined;
|
|
1306
|
+
const match = /^\\s*(\\d+)\\s*(ms|s|m|h|d)\\s*$/.exec(text);
|
|
1307
|
+
if (!match) return undefined;
|
|
1308
|
+
const units: Record<string, number> = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
1309
|
+
return Number(match[1]) * units[match[2]];
|
|
1310
|
+
}
|
|
1311
|
+
`.trim()
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
type: "NodeType",
|
|
1315
|
+
name: "sleep",
|
|
1316
|
+
functionName: "sleep",
|
|
1317
|
+
isAsync: true,
|
|
1318
|
+
receivesAbortSignal: false,
|
|
1319
|
+
receivesRuntime: true,
|
|
1320
|
+
durableGate: "timer",
|
|
1321
|
+
hasSuccessPort: true,
|
|
1322
|
+
hasFailurePort: true,
|
|
1323
|
+
executeWhen: "CONJUNCTION",
|
|
1324
|
+
variant: "FUNCTION",
|
|
1325
|
+
inputs: {
|
|
1326
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
1327
|
+
duration: { dataType: "STRING", label: 'How long the run sleeps before it goes on (e.g. "30s", "2h", "3d")', tsType: "string" }
|
|
1328
|
+
},
|
|
1329
|
+
outputs: {
|
|
1330
|
+
wokeAt: { dataType: "STRING", label: "When the run went on, as an ISO 8601 time", tsType: "string" },
|
|
1331
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
1332
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true }
|
|
1333
|
+
},
|
|
1334
|
+
helperText: `
|
|
1335
|
+
function __fw_getMockConfig(runtime?: NodeExecutionRuntime): FwMockConfig | undefined {
|
|
1336
|
+
return runtime?.runtime.services.mocks;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function __fw_lookupMock<T>(
|
|
1340
|
+
section: Record<string, T> | undefined,
|
|
1341
|
+
key: string,
|
|
1342
|
+
runtime?: NodeExecutionRuntime,
|
|
1343
|
+
): T | undefined {
|
|
1344
|
+
if (!section) return undefined;
|
|
1345
|
+
|
|
1346
|
+
const nodeId = runtime?.nodeId;
|
|
1347
|
+
if (nodeId) {
|
|
1348
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
1349
|
+
if (qualified !== undefined) return qualified;
|
|
1350
|
+
const any = section[\`\${nodeId}:*\`];
|
|
1351
|
+
if (any !== undefined) return any;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return section[key];
|
|
1355
|
+
}
|
|
1356
|
+
`.trim(),
|
|
1357
|
+
helperTextProduction: void 0,
|
|
1358
|
+
functionText: `
|
|
1359
|
+
async function sleep(
|
|
1360
|
+
execute: boolean,
|
|
1361
|
+
duration: string,
|
|
1362
|
+
runtime?: NodeExecutionRuntime,
|
|
1363
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; wokeAt: string }> {
|
|
1364
|
+
if (!execute) return { onSuccess: false, onFailure: false, wokeAt: '' };
|
|
1365
|
+
|
|
1366
|
+
// A timer gate. The compiler replaces this call with a durable yield, and
|
|
1367
|
+
// whoever keeps the run (the coordinator's clock, or a host of its own)
|
|
1368
|
+
// resumes it once \`duration\` has passed; under test \`fast\` wakes it at
|
|
1369
|
+
// once (see \`FwMockConfig\` in \`src/built-in-nodes/mock-types.ts\`).
|
|
1370
|
+
// Reaching this body means the generated program did not apply the gate
|
|
1371
|
+
// boundary, so it fails closed. A wait that holds the process is what
|
|
1372
|
+
// \`delay\` is for.
|
|
1373
|
+
void duration;
|
|
1374
|
+
void runtime;
|
|
1375
|
+
throw new Error('sleep requires a generated durable timer gate');
|
|
1376
|
+
}
|
|
1377
|
+
`.trim(),
|
|
1378
|
+
functionTextProduction: `
|
|
1379
|
+
async function sleep(
|
|
1380
|
+
execute: boolean,
|
|
1381
|
+
duration: string,
|
|
1382
|
+
runtime?: NodeExecutionRuntime,
|
|
1383
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; wokeAt: string }> {
|
|
1384
|
+
if (!execute) return { onSuccess: false, onFailure: false, wokeAt: '' };
|
|
1385
|
+
|
|
1386
|
+
// A timer gate. The compiler replaces this call with a durable yield, and
|
|
1387
|
+
// whoever keeps the run (the coordinator's clock, or a host of its own)
|
|
1388
|
+
// resumes it once \`duration\` has passed; under test \`fast\` wakes it at
|
|
1389
|
+
// Reaching this body means the generated program did not apply the gate
|
|
1390
|
+
// boundary, so it fails closed. A wait that holds the process is what
|
|
1391
|
+
// \`delay\` is for.
|
|
1392
|
+
void duration;
|
|
1393
|
+
void runtime;
|
|
1394
|
+
throw new Error('sleep requires a generated durable timer gate');
|
|
1395
|
+
}
|
|
1396
|
+
`.trim()
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
type: "NodeType",
|
|
1400
|
+
name: "waitForEvent",
|
|
1401
|
+
functionName: "waitForEvent",
|
|
1402
|
+
isAsync: true,
|
|
1403
|
+
receivesAbortSignal: false,
|
|
1404
|
+
receivesRuntime: true,
|
|
1405
|
+
durableGate: "input",
|
|
1406
|
+
hasSuccessPort: true,
|
|
1407
|
+
hasFailurePort: true,
|
|
1408
|
+
executeWhen: "CONJUNCTION",
|
|
1409
|
+
variant: "FUNCTION",
|
|
1410
|
+
inputs: {
|
|
1411
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
1412
|
+
eventName: { dataType: "STRING", label: 'Event name to wait for (e.g. "app/approval.received")', tsType: "string" },
|
|
1413
|
+
match: { dataType: "STRING", label: 'Field to match between trigger and waited event (e.g. "data.requestId")', tsType: "string", optional: true },
|
|
1414
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "24h", "7d"). Empty = no timeout', tsType: "string", optional: true }
|
|
1415
|
+
},
|
|
1416
|
+
outputs: {
|
|
1417
|
+
eventData: { dataType: "OBJECT", label: "The received event's data payload", tsType: "object" },
|
|
1418
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
1419
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true }
|
|
1420
|
+
},
|
|
1421
|
+
helperText: `
|
|
1422
|
+
function __fw_getMockConfig(runtime?: NodeExecutionRuntime): FwMockConfig | undefined {
|
|
1423
|
+
return runtime?.runtime.services.mocks;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
function __fw_lookupMock<T>(
|
|
1427
|
+
section: Record<string, T> | undefined,
|
|
1428
|
+
key: string,
|
|
1429
|
+
runtime?: NodeExecutionRuntime,
|
|
1430
|
+
): T | undefined {
|
|
1431
|
+
if (!section) return undefined;
|
|
1432
|
+
|
|
1433
|
+
const nodeId = runtime?.nodeId;
|
|
1434
|
+
if (nodeId) {
|
|
1435
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
1436
|
+
if (qualified !== undefined) return qualified;
|
|
1437
|
+
const any = section[\`\${nodeId}:*\`];
|
|
1438
|
+
if (any !== undefined) return any;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
return section[key];
|
|
1442
|
+
}
|
|
1443
|
+
`.trim(),
|
|
1444
|
+
helperTextProduction: void 0,
|
|
1445
|
+
functionText: `
|
|
1446
|
+
async function waitForEvent(
|
|
1447
|
+
execute: boolean,
|
|
1448
|
+
eventName: string,
|
|
1449
|
+
match?: string,
|
|
1450
|
+
timeout?: string,
|
|
1451
|
+
runtime?: NodeExecutionRuntime,
|
|
1452
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; eventData: object }> {
|
|
1453
|
+
if (!execute) return { onSuccess: false, onFailure: false, eventData: {} };
|
|
1454
|
+
|
|
1455
|
+
// An input gate. The compiler replaces this call with a durable yield, and
|
|
1456
|
+
// the engine answers the gate: from a person, or under test from the run's
|
|
1457
|
+
// canned answers (see \`FwMockConfig\` in \`src/built-in-nodes/mock-types.ts\`
|
|
1458
|
+
// for how those are read). Reaching this body means the generated program
|
|
1459
|
+
// did not apply the gate boundary, so it fails closed.
|
|
1460
|
+
void eventName;
|
|
1461
|
+
void match;
|
|
1462
|
+
void timeout;
|
|
1463
|
+
void runtime;
|
|
1464
|
+
throw new Error('waitForEvent requires a generated durable input gate');
|
|
1465
|
+
}
|
|
1466
|
+
`.trim(),
|
|
1467
|
+
functionTextProduction: `
|
|
1468
|
+
async function waitForEvent(
|
|
1469
|
+
execute: boolean,
|
|
1470
|
+
eventName: string,
|
|
1471
|
+
match?: string,
|
|
1472
|
+
timeout?: string,
|
|
1473
|
+
runtime?: NodeExecutionRuntime,
|
|
1474
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; eventData: object }> {
|
|
1475
|
+
if (!execute) return { onSuccess: false, onFailure: false, eventData: {} };
|
|
1476
|
+
|
|
1477
|
+
// An input gate. The compiler replaces this call with a durable yield, and
|
|
1478
|
+
// the engine answers the gate: from a person, or under test from the run's
|
|
1479
|
+
// for how those are read). Reaching this body means the generated program
|
|
1480
|
+
// did not apply the gate boundary, so it fails closed.
|
|
1481
|
+
void eventName;
|
|
1482
|
+
void match;
|
|
1483
|
+
void timeout;
|
|
1484
|
+
void runtime;
|
|
1485
|
+
throw new Error('waitForEvent requires a generated durable input gate');
|
|
1486
|
+
}
|
|
1487
|
+
`.trim()
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
type: "NodeType",
|
|
1491
|
+
name: "invokeWorkflow",
|
|
1492
|
+
functionName: "invokeWorkflow",
|
|
1493
|
+
isAsync: true,
|
|
1494
|
+
receivesAbortSignal: true,
|
|
1495
|
+
receivesRuntime: true,
|
|
1496
|
+
durablePure: true,
|
|
1497
|
+
hasSuccessPort: true,
|
|
1498
|
+
hasFailurePort: true,
|
|
1499
|
+
executeWhen: "CONJUNCTION",
|
|
1500
|
+
variant: "FUNCTION",
|
|
1501
|
+
inputs: {
|
|
1502
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
1503
|
+
functionId: { dataType: "STRING", label: 'Function ID of the workflow to invoke (e.g. "my-service/sub-workflow")', tsType: "string" },
|
|
1504
|
+
payload: { dataType: "OBJECT", label: "Data to pass as event.data to the invoked function", tsType: "object" },
|
|
1505
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "1h")', tsType: "string", optional: true }
|
|
1506
|
+
},
|
|
1507
|
+
outputs: {
|
|
1508
|
+
result: { dataType: "OBJECT", label: "Return value from the invoked function", tsType: "object" },
|
|
1509
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
1510
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true }
|
|
1511
|
+
},
|
|
1512
|
+
helperText: `
|
|
1513
|
+
function __fw_getMockConfig(runtime?: NodeExecutionRuntime): FwMockConfig | undefined {
|
|
1514
|
+
return runtime?.runtime.services.mocks;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function __fw_lookupMock<T>(
|
|
1518
|
+
section: Record<string, T> | undefined,
|
|
1519
|
+
key: string,
|
|
1520
|
+
runtime?: NodeExecutionRuntime,
|
|
1521
|
+
): T | undefined {
|
|
1522
|
+
if (!section) return undefined;
|
|
1523
|
+
|
|
1524
|
+
const nodeId = runtime?.nodeId;
|
|
1525
|
+
if (nodeId) {
|
|
1526
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
1527
|
+
if (qualified !== undefined) return qualified;
|
|
1528
|
+
const any = section[\`\${nodeId}:*\`];
|
|
1529
|
+
if (any !== undefined) return any;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
return section[key];
|
|
1533
|
+
}
|
|
1534
|
+
`.trim(),
|
|
1535
|
+
helperTextProduction: void 0,
|
|
1536
|
+
functionText: `
|
|
1537
|
+
async function invokeWorkflow(
|
|
1538
|
+
execute: boolean,
|
|
1539
|
+
functionId: string,
|
|
1540
|
+
payload: object,
|
|
1541
|
+
timeout?: string,
|
|
1542
|
+
abortSignal?: AbortSignal,
|
|
1543
|
+
runtime?: NodeExecutionRuntime,
|
|
1544
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; result: object }> {
|
|
1545
|
+
if (!execute) return { onSuccess: false, onFailure: false, result: {} };
|
|
1546
|
+
|
|
1547
|
+
const mocks = __fw_getMockConfig(runtime);
|
|
1548
|
+
if (mocks) {
|
|
1549
|
+
// Mock mode: look up result by functionId (supports instance-qualified keys)
|
|
1550
|
+
const mockResult = __fw_lookupMock(mocks.invocations, functionId, runtime);
|
|
1551
|
+
if (mockResult !== undefined) {
|
|
1552
|
+
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
1553
|
+
}
|
|
1554
|
+
// No mock data for this functionId, so simulate failure
|
|
1555
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Check local workflow registry (populated by executeWorkflow)
|
|
1559
|
+
const registry = runtime?.runtime.services.workflowRegistry;
|
|
1560
|
+
if (registry?.[functionId]) {
|
|
1561
|
+
const nodeRuntime = runtime!;
|
|
1562
|
+
if (
|
|
1563
|
+
!Number.isSafeInteger(nodeRuntime.recursionDepth) ||
|
|
1564
|
+
nodeRuntime.recursionDepth < 0 ||
|
|
1565
|
+
nodeRuntime.recursionDepth >= 999
|
|
1566
|
+
) {
|
|
1567
|
+
throw new Error('Max recursion depth exceeded (1000) in dynamic workflow invocation');
|
|
1568
|
+
}
|
|
1569
|
+
try {
|
|
1570
|
+
const result = await registry[functionId](
|
|
1571
|
+
true,
|
|
1572
|
+
{ ...payload, __rd__: nodeRuntime.recursionDepth + 1 },
|
|
1573
|
+
nodeRuntime.createNestedRuntime(functionId),
|
|
1574
|
+
);
|
|
1575
|
+
return { onSuccess: true, onFailure: false, result: (result as object) ?? {} };
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
const controlFlowCode =
|
|
1578
|
+
typeof error === 'object' && error !== null
|
|
1579
|
+
? (error as { code?: unknown }).code
|
|
1580
|
+
: undefined;
|
|
1581
|
+
if (
|
|
1582
|
+
CancellationError.isCancellationError(error) ||
|
|
1583
|
+
controlFlowCode === 'FLOW_WEAVER_DURABLE_GATE_YIELD' ||
|
|
1584
|
+
controlFlowCode === 'FLOW_WEAVER_AMBIGUOUS_EFFECT'
|
|
1585
|
+
) {
|
|
1586
|
+
throw error;
|
|
1587
|
+
}
|
|
1588
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// No mocks, no registry match: original no-op behavior (always succeeds)
|
|
1593
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
1594
|
+
}
|
|
1595
|
+
`.trim(),
|
|
1596
|
+
functionTextProduction: `
|
|
1597
|
+
async function invokeWorkflow(
|
|
1598
|
+
execute: boolean,
|
|
1599
|
+
functionId: string,
|
|
1600
|
+
payload: object,
|
|
1601
|
+
timeout?: string,
|
|
1602
|
+
abortSignal?: AbortSignal,
|
|
1603
|
+
runtime?: NodeExecutionRuntime,
|
|
1604
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; result: object }> {
|
|
1605
|
+
if (!execute) return { onSuccess: false, onFailure: false, result: {} };
|
|
1606
|
+
|
|
1607
|
+
|
|
1608
|
+
// Check local workflow registry (populated by executeWorkflow)
|
|
1609
|
+
const registry = runtime?.runtime.services.workflowRegistry;
|
|
1610
|
+
if (registry?.[functionId]) {
|
|
1611
|
+
const nodeRuntime = runtime!;
|
|
1612
|
+
if (
|
|
1613
|
+
!Number.isSafeInteger(nodeRuntime.recursionDepth) ||
|
|
1614
|
+
nodeRuntime.recursionDepth < 0 ||
|
|
1615
|
+
nodeRuntime.recursionDepth >= 999
|
|
1616
|
+
) {
|
|
1617
|
+
throw new Error('Max recursion depth exceeded (1000) in dynamic workflow invocation');
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
const result = await registry[functionId](
|
|
1621
|
+
true,
|
|
1622
|
+
{ ...payload, __rd__: nodeRuntime.recursionDepth + 1 },
|
|
1623
|
+
nodeRuntime.createNestedRuntime(functionId),
|
|
1624
|
+
);
|
|
1625
|
+
return { onSuccess: true, onFailure: false, result: (result as object) ?? {} };
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
const controlFlowCode =
|
|
1628
|
+
typeof error === 'object' && error !== null
|
|
1629
|
+
? (error as { code?: unknown }).code
|
|
1630
|
+
: undefined;
|
|
1631
|
+
if (
|
|
1632
|
+
CancellationError.isCancellationError(error) ||
|
|
1633
|
+
controlFlowCode === 'FLOW_WEAVER_DURABLE_GATE_YIELD' ||
|
|
1634
|
+
controlFlowCode === 'FLOW_WEAVER_AMBIGUOUS_EFFECT'
|
|
1635
|
+
) {
|
|
1636
|
+
throw error;
|
|
1637
|
+
}
|
|
1638
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
1643
|
+
}
|
|
1644
|
+
`.trim()
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
type: "NodeType",
|
|
1648
|
+
name: "waitForAgent",
|
|
1649
|
+
functionName: "waitForAgent",
|
|
1650
|
+
isAsync: true,
|
|
1651
|
+
receivesAbortSignal: true,
|
|
1652
|
+
receivesRuntime: true,
|
|
1653
|
+
durableGate: "agent",
|
|
1654
|
+
hasSuccessPort: true,
|
|
1655
|
+
hasFailurePort: true,
|
|
1656
|
+
executeWhen: "CONJUNCTION",
|
|
1657
|
+
variant: "FUNCTION",
|
|
1658
|
+
inputs: {
|
|
1659
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
1660
|
+
agentId: { dataType: "STRING", label: "Agent/task identifier", tsType: "string" },
|
|
1661
|
+
context: { dataType: "OBJECT", label: "Context data to send to the agent", tsType: "object" },
|
|
1662
|
+
prompt: { dataType: "STRING", label: "Message to display when requesting input", tsType: "string", optional: true }
|
|
1663
|
+
},
|
|
1664
|
+
outputs: {
|
|
1665
|
+
agentResult: { dataType: "OBJECT", label: "Result returned by the agent", tsType: "object" },
|
|
1666
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
1667
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true }
|
|
1668
|
+
},
|
|
1669
|
+
helperText: `
|
|
1670
|
+
function __fw_getMockConfig(runtime?: NodeExecutionRuntime): FwMockConfig | undefined {
|
|
1671
|
+
return runtime?.runtime.services.mocks;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function __fw_lookupMock<T>(
|
|
1675
|
+
section: Record<string, T> | undefined,
|
|
1676
|
+
key: string,
|
|
1677
|
+
runtime?: NodeExecutionRuntime,
|
|
1678
|
+
): T | undefined {
|
|
1679
|
+
if (!section) return undefined;
|
|
1680
|
+
|
|
1681
|
+
const nodeId = runtime?.nodeId;
|
|
1682
|
+
if (nodeId) {
|
|
1683
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
1684
|
+
if (qualified !== undefined) return qualified;
|
|
1685
|
+
const any = section[\`\${nodeId}:*\`];
|
|
1686
|
+
if (any !== undefined) return any;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
return section[key];
|
|
1690
|
+
}
|
|
1691
|
+
`.trim(),
|
|
1692
|
+
helperTextProduction: void 0,
|
|
1693
|
+
functionText: `
|
|
1694
|
+
async function waitForAgent(
|
|
1695
|
+
execute: boolean,
|
|
1696
|
+
agentId: string,
|
|
1697
|
+
context: object,
|
|
1698
|
+
prompt?: string,
|
|
1699
|
+
_abortSignal?: AbortSignal,
|
|
1700
|
+
runtime?: NodeExecutionRuntime,
|
|
1701
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; agentResult: object }> {
|
|
1702
|
+
if (!execute) return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
1703
|
+
|
|
1704
|
+
// An agent gate. The compiler replaces this call with a durable yield, and
|
|
1705
|
+
// the engine answers the gate: from a person, or under test from the run's
|
|
1706
|
+
// canned answers (see \`FwMockConfig\` in \`src/built-in-nodes/mock-types.ts\`
|
|
1707
|
+
// for how those are read). Reaching this body means the generated program
|
|
1708
|
+
// did not apply the gate boundary, so it fails closed.
|
|
1709
|
+
void agentId;
|
|
1710
|
+
void context;
|
|
1711
|
+
void prompt;
|
|
1712
|
+
void runtime;
|
|
1713
|
+
throw new Error('waitForAgent requires a generated durable agent gate');
|
|
1714
|
+
}
|
|
1715
|
+
`.trim(),
|
|
1716
|
+
functionTextProduction: `
|
|
1717
|
+
async function waitForAgent(
|
|
1718
|
+
execute: boolean,
|
|
1719
|
+
agentId: string,
|
|
1720
|
+
context: object,
|
|
1721
|
+
prompt?: string,
|
|
1722
|
+
_abortSignal?: AbortSignal,
|
|
1723
|
+
runtime?: NodeExecutionRuntime,
|
|
1724
|
+
): Promise<{ onSuccess: boolean; onFailure: boolean; agentResult: object }> {
|
|
1725
|
+
if (!execute) return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
1726
|
+
|
|
1727
|
+
// An agent gate. The compiler replaces this call with a durable yield, and
|
|
1728
|
+
// the engine answers the gate: from a person, or under test from the run's
|
|
1729
|
+
// for how those are read). Reaching this body means the generated program
|
|
1730
|
+
// did not apply the gate boundary, so it fails closed.
|
|
1731
|
+
void agentId;
|
|
1732
|
+
void context;
|
|
1733
|
+
void prompt;
|
|
1734
|
+
void runtime;
|
|
1735
|
+
throw new Error('waitForAgent requires a generated durable agent gate');
|
|
1736
|
+
}
|
|
1737
|
+
`.trim()
|
|
1738
|
+
}
|
|
1739
|
+
];
|
|
1740
|
+
|
|
1741
|
+
// src/parser/port-inference.ts
|
|
1742
|
+
import * as path from "node:path";
|
|
1743
|
+
function parseStartPorts(fn, config) {
|
|
1744
|
+
const ports = {};
|
|
1745
|
+
const params = fn.getParameters();
|
|
1746
|
+
if (params.length === 0) {
|
|
1747
|
+
ports.execute = { dataType: "STEP", label: "Execute" };
|
|
1748
|
+
return ports;
|
|
1749
|
+
}
|
|
1750
|
+
const firstParam = params[0];
|
|
1751
|
+
const firstParamName = firstParam.getName();
|
|
1752
|
+
const firstParamType = firstParam.getType();
|
|
1753
|
+
const firstParamTypeText = firstParamType.getText();
|
|
1754
|
+
const isExecutePort2 = firstParamName === "execute" && (firstParamTypeText === "boolean" || firstParamTypeText === "any" && config?.startPorts?.["execute"]);
|
|
1755
|
+
if (isExecutePort2) {
|
|
1756
|
+
if (config?.startPorts && config.startPorts["execute"]) {
|
|
1757
|
+
ports.execute = {
|
|
1758
|
+
dataType: "STEP",
|
|
1759
|
+
tsType: "boolean",
|
|
1760
|
+
...config.startPorts["execute"]
|
|
1761
|
+
};
|
|
1762
|
+
} else {
|
|
1763
|
+
ports.execute = { dataType: "STEP", tsType: "boolean", label: "Execute" };
|
|
1764
|
+
}
|
|
1765
|
+
if (params.length > 1) {
|
|
1766
|
+
const dataParams = params.slice(1).filter(
|
|
1767
|
+
(p) => p.getName() !== "__runtime__" && !["AbortSignal", "TDebugger"].includes(p.getType().getText(p))
|
|
1768
|
+
);
|
|
1769
|
+
const shouldExpandProperties = dataParams.length === 1 && isExpandableObjectType(dataParams[0].getType());
|
|
1770
|
+
const declaresNoPorts = dataParams.length === 1 && isPropertylessObjectType(dataParams[0].getType());
|
|
1771
|
+
if (declaresNoPorts) {
|
|
1772
|
+
} else if (shouldExpandProperties) {
|
|
1773
|
+
const dataParam = dataParams[0];
|
|
1774
|
+
const dataParamType = dataParam.getType();
|
|
1775
|
+
const properties = dataParamType.getProperties();
|
|
1776
|
+
properties.forEach((prop) => {
|
|
1777
|
+
const propName = prop.getName();
|
|
1778
|
+
const propType = prop.getTypeAtLocation(dataParam);
|
|
1779
|
+
const portType = inferPortType(propType);
|
|
1780
|
+
const propTypeText = propType.getText();
|
|
1781
|
+
const tsSchema = portType === "OBJECT" ? extractTypeSchema(propType) : void 0;
|
|
1782
|
+
const startPortConfig = config?.startPorts?.[propName];
|
|
1783
|
+
ports[propName] = {
|
|
1784
|
+
dataType: portType,
|
|
1785
|
+
label: startPortConfig?.label || capitalize(propName),
|
|
1786
|
+
...startPortConfig?.optional === true && { optional: true },
|
|
1787
|
+
...startPortConfig !== void 0 && Object.hasOwn(startPortConfig, "default") ? { default: startPortConfig.default } : {},
|
|
1788
|
+
...startPortConfig?.metadata && { metadata: startPortConfig.metadata },
|
|
1789
|
+
...propTypeText && { tsType: propTypeText },
|
|
1790
|
+
...tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }
|
|
1791
|
+
};
|
|
1792
|
+
});
|
|
1793
|
+
} else {
|
|
1794
|
+
for (const param of dataParams) {
|
|
1795
|
+
const paramName = param.getName();
|
|
1796
|
+
const paramType = param.getType();
|
|
1797
|
+
const portType = inferPortType(paramType);
|
|
1798
|
+
const rawParamTypeText = paramType.getText(param);
|
|
1799
|
+
const paramTypeText = param.isOptional() || param.hasInitializer() ? stripOptionalUndefined(rawParamTypeText) : rawParamTypeText;
|
|
1800
|
+
const tsSchema = portType === "OBJECT" ? extractTypeSchema(paramType) : void 0;
|
|
1801
|
+
const startPortConfig = config?.startPorts?.[paramName];
|
|
1802
|
+
ports[paramName] = {
|
|
1803
|
+
dataType: portType,
|
|
1804
|
+
label: startPortConfig?.label || capitalize(paramName),
|
|
1805
|
+
...startPortConfig?.optional === true && { optional: true },
|
|
1806
|
+
...startPortConfig !== void 0 && Object.hasOwn(startPortConfig, "default") ? { default: startPortConfig.default } : {},
|
|
1807
|
+
...startPortConfig?.metadata && { metadata: startPortConfig.metadata },
|
|
1808
|
+
...paramTypeText && { tsType: paramTypeText },
|
|
1809
|
+
...tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
} else {
|
|
1815
|
+
throw new Error(
|
|
1816
|
+
`Invalid workflow function signature for "${fn.getName()}". Expected first parameter to be "execute: boolean", but got "${firstParamName}: ${firstParamTypeText}". Correct format: export function ${fn.getName()}(execute: boolean, params: {...}): { onSuccess: boolean; onFailure: boolean; ... }`
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
assignImplicitPortOrders(ports);
|
|
1820
|
+
return ports;
|
|
1821
|
+
}
|
|
1822
|
+
function parseExitPorts(fn, config, warnings) {
|
|
1823
|
+
const ports = {};
|
|
1824
|
+
let returnType = fn.getReturnType();
|
|
1825
|
+
const typeText = returnType?.getText();
|
|
1826
|
+
const sourceFile = fn.getSourceFile();
|
|
1827
|
+
const filePath = sourceFile?.getFilePath() || "unknown file";
|
|
1828
|
+
const fileName = path.basename(filePath);
|
|
1829
|
+
if (!typeText || typeText === "void") {
|
|
1830
|
+
warnings?.push(
|
|
1831
|
+
`Could not determine the return type of workflow "${fn.getName()}" in ${fileName}, so it has no Exit ports. Add an explicit return type like: Promise<{ onSuccess: boolean; onFailure: boolean }>`
|
|
1832
|
+
);
|
|
1833
|
+
return ports;
|
|
1834
|
+
}
|
|
1835
|
+
if (typeText.startsWith("Promise<")) {
|
|
1836
|
+
const typeArgs = returnType.getTypeArguments();
|
|
1837
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
1838
|
+
returnType = typeArgs[0];
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const properties = returnType.getProperties();
|
|
1842
|
+
properties.forEach((prop) => {
|
|
1843
|
+
const propName = prop.getName();
|
|
1844
|
+
if (propName === "onSuccess" || propName === "onFailure") {
|
|
1845
|
+
const returnPortConfig = config?.returnPorts?.[propName];
|
|
1846
|
+
ports[propName] = {
|
|
1847
|
+
dataType: "STEP",
|
|
1848
|
+
label: returnPortConfig?.label || (propName === "onSuccess" ? "On Success" : "On Failure"),
|
|
1849
|
+
isControlFlow: true,
|
|
1850
|
+
...propName === "onFailure" && { failure: true },
|
|
1851
|
+
...returnPortConfig?.metadata && { metadata: returnPortConfig.metadata }
|
|
1852
|
+
};
|
|
1853
|
+
} else {
|
|
1854
|
+
const propType = prop.getTypeAtLocation(fn.getTypeResolutionNode());
|
|
1855
|
+
const portType = inferPortType(propType);
|
|
1856
|
+
const propTypeText = propType.getText();
|
|
1857
|
+
const tsSchema = portType === "OBJECT" ? extractTypeSchema(propType) : void 0;
|
|
1858
|
+
const returnPortConfig = config?.returnPorts?.[propName];
|
|
1859
|
+
ports[propName] = {
|
|
1860
|
+
dataType: portType,
|
|
1861
|
+
label: returnPortConfig?.label || capitalize(propName),
|
|
1862
|
+
...returnPortConfig?.metadata && { metadata: returnPortConfig.metadata },
|
|
1863
|
+
// Include original TS type for rich type display (e.g., "ResearchReport" instead of "object")
|
|
1864
|
+
...propTypeText && propTypeText !== portType.toLowerCase() && { tsType: propTypeText },
|
|
1865
|
+
// Include schema breakdown for complex types
|
|
1866
|
+
...tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
assignImplicitPortOrders(ports);
|
|
1871
|
+
return ports;
|
|
1872
|
+
}
|
|
1873
|
+
function extractTypeSchema(tsType) {
|
|
1874
|
+
const schema = {};
|
|
1875
|
+
const properties = tsType.getProperties();
|
|
1876
|
+
if (!properties || properties.length === 0) {
|
|
1877
|
+
return void 0;
|
|
1878
|
+
}
|
|
1879
|
+
for (const prop of properties) {
|
|
1880
|
+
const propName = prop.getName();
|
|
1881
|
+
if (propName.startsWith("_")) continue;
|
|
1882
|
+
const propType = prop.getValueDeclaration()?.getType();
|
|
1883
|
+
if (propType) {
|
|
1884
|
+
schema[propName] = propType.getText();
|
|
1885
|
+
} else {
|
|
1886
|
+
const declarations = prop.getDeclarations();
|
|
1887
|
+
if (declarations && declarations.length > 0) {
|
|
1888
|
+
const decl = declarations[0];
|
|
1889
|
+
schema[propName] = decl.getType().getText();
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return Object.keys(schema).length > 0 ? schema : void 0;
|
|
1894
|
+
}
|
|
1895
|
+
function isPropertylessObjectType(tsType) {
|
|
1896
|
+
return tsType.isObject() && !tsType.isArray() && tsType.getCallSignatures().length === 0 && tsType.getProperties().length === 0;
|
|
1897
|
+
}
|
|
1898
|
+
function isExpandableObjectType(tsType) {
|
|
1899
|
+
const typeText = tsType.getText();
|
|
1900
|
+
const primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "unknown", "never", "object", "Object"]);
|
|
1901
|
+
if (primitiveTypes.has(typeText)) return false;
|
|
1902
|
+
if (typeText.endsWith("[]") || typeText.startsWith("Array<")) return false;
|
|
1903
|
+
return tsType.isObject() && tsType.getProperties().length > 0;
|
|
1904
|
+
}
|
|
1905
|
+
function inferPortType(tsType) {
|
|
1906
|
+
const typeText = tsType.getText();
|
|
1907
|
+
return inferDataTypeFromTS(typeText);
|
|
1908
|
+
}
|
|
1909
|
+
function capitalize(str) {
|
|
1910
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// src/parser/durable-effect-contract.ts
|
|
1914
|
+
function jsDocTagComment(fn, tagName) {
|
|
1915
|
+
for (const doc of fn.getJsDocs()) {
|
|
1916
|
+
for (const tag of doc.getTags()) {
|
|
1917
|
+
if (tag.getTagName() === tagName) {
|
|
1918
|
+
return tag.getCommentText?.()?.trim() ?? "";
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return void 0;
|
|
1923
|
+
}
|
|
1924
|
+
function hasJsDocTag(fn, tagName) {
|
|
1925
|
+
return jsDocTagComment(fn, tagName) !== void 0;
|
|
1926
|
+
}
|
|
1927
|
+
function durableGateKind(fn) {
|
|
1928
|
+
const kind = jsDocTagComment(fn, "durableGate")?.split(/\s+/)[0];
|
|
1929
|
+
return kind === "approval" || kind === "input" || kind === "agent" || kind === "timer" ? kind : void 0;
|
|
1930
|
+
}
|
|
1931
|
+
function describeType(type, fn) {
|
|
1932
|
+
return type.getText(fn.getTypeResolutionNode());
|
|
1933
|
+
}
|
|
1934
|
+
function nonUndefinedUnionMembers(type) {
|
|
1935
|
+
const members = type.isUnion() ? type.getUnionTypes() : [type];
|
|
1936
|
+
return members.filter((member) => !member.isUndefined());
|
|
1937
|
+
}
|
|
1938
|
+
function wireTypeFailure(type, path4 = "$", seen = /* @__PURE__ */ new Set()) {
|
|
1939
|
+
const identity = type.compilerType;
|
|
1940
|
+
if (seen.has(identity)) return void 0;
|
|
1941
|
+
seen.add(identity);
|
|
1942
|
+
const members = nonUndefinedUnionMembers(type);
|
|
1943
|
+
if (members.length === 0) return `${path4} has no defined member`;
|
|
1944
|
+
if (members.length > 1 || type.isUnion()) {
|
|
1945
|
+
for (const member of members) {
|
|
1946
|
+
const failure = wireTypeFailure(member, path4, new Set(seen));
|
|
1947
|
+
if (failure !== void 0) return failure;
|
|
1948
|
+
}
|
|
1949
|
+
return void 0;
|
|
1950
|
+
}
|
|
1951
|
+
const candidate = members[0];
|
|
1952
|
+
if (candidate.isAny() || candidate.isUnknown() || candidate.isNever() || candidate.isVoid() || candidate.isBigInt() || candidate.isBigIntLiteral()) {
|
|
1953
|
+
return `${path4} is ${candidate.getText()}`;
|
|
1954
|
+
}
|
|
1955
|
+
if (candidate.isNull() || candidate.isString() || candidate.isStringLiteral() || candidate.isNumber() || candidate.isNumberLiteral() || candidate.isBoolean() || candidate.isBooleanLiteral() || candidate.isTemplateLiteral()) {
|
|
1956
|
+
return void 0;
|
|
1957
|
+
}
|
|
1958
|
+
if (candidate.isTuple()) {
|
|
1959
|
+
for (const [index, element] of candidate.getTupleElements().entries()) {
|
|
1960
|
+
const failure = wireTypeFailure(element, `${path4}[${index}]`, new Set(seen));
|
|
1961
|
+
if (failure !== void 0) return failure;
|
|
1962
|
+
}
|
|
1963
|
+
return void 0;
|
|
1964
|
+
}
|
|
1965
|
+
if (candidate.isArray() || candidate.isReadonlyArray()) {
|
|
1966
|
+
const element = candidate.getArrayElementType();
|
|
1967
|
+
return element === void 0 ? `${path4} has no array element type` : wireTypeFailure(element, `${path4}[]`, new Set(seen));
|
|
1968
|
+
}
|
|
1969
|
+
if (!candidate.isObject() || candidate.isClass()) return `${path4} is not a plain object`;
|
|
1970
|
+
if (candidate.getCallSignatures().length > 0 || candidate.getConstructSignatures().length > 0) {
|
|
1971
|
+
return `${path4} is callable or constructable`;
|
|
1972
|
+
}
|
|
1973
|
+
const indexTypes = [
|
|
1974
|
+
candidate.getStringIndexType(),
|
|
1975
|
+
candidate.getNumberIndexType()
|
|
1976
|
+
].filter((indexType) => indexType !== void 0);
|
|
1977
|
+
for (const indexType of indexTypes) {
|
|
1978
|
+
const failure = wireTypeFailure(indexType, `${path4}[key]`, new Set(seen));
|
|
1979
|
+
if (failure !== void 0) return failure;
|
|
1980
|
+
}
|
|
1981
|
+
for (const property of candidate.getProperties()) {
|
|
1982
|
+
const location = property.getValueDeclaration() ?? property.getDeclarations()[0];
|
|
1983
|
+
if (location === void 0) return `${path4}.${property.getName()} has no declaration`;
|
|
1984
|
+
const failure = wireTypeFailure(
|
|
1985
|
+
property.getTypeAtLocation(location),
|
|
1986
|
+
`${path4}.${property.getName()}`,
|
|
1987
|
+
new Set(seen)
|
|
1988
|
+
);
|
|
1989
|
+
if (failure !== void 0) return failure;
|
|
1990
|
+
}
|
|
1991
|
+
return void 0;
|
|
1992
|
+
}
|
|
1993
|
+
function matchesPortType(type, dataType) {
|
|
1994
|
+
const members = nonUndefinedUnionMembers(type);
|
|
1995
|
+
if (members.length === 0) return false;
|
|
1996
|
+
return members.every((member) => {
|
|
1997
|
+
switch (dataType) {
|
|
1998
|
+
case "STRING":
|
|
1999
|
+
return member.isString() || member.isStringLiteral() || member.isTemplateLiteral();
|
|
2000
|
+
case "NUMBER":
|
|
2001
|
+
return member.isNumber() || member.isNumberLiteral();
|
|
2002
|
+
case "BOOLEAN":
|
|
2003
|
+
case "STEP":
|
|
2004
|
+
return member.isBoolean() || member.isBooleanLiteral();
|
|
2005
|
+
case "ARRAY":
|
|
2006
|
+
return member.isArray() || member.isReadonlyArray() || member.isTuple();
|
|
2007
|
+
case "OBJECT":
|
|
2008
|
+
return member.isObject() && !member.isArray() && !member.isReadonlyArray();
|
|
2009
|
+
case "FUNCTION":
|
|
2010
|
+
return member.getCallSignatures().length > 0;
|
|
2011
|
+
case "ANY":
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
function validateEnvelopeMember(envelope, outputs, fn) {
|
|
2017
|
+
const diagnostics = [];
|
|
2018
|
+
if (!envelope.isObject() || envelope.isArray() || envelope.isReadonlyArray()) {
|
|
2019
|
+
return [
|
|
2020
|
+
`return type must be exactly { receipt, result }, got ${describeType(envelope, fn)}`
|
|
2021
|
+
];
|
|
2022
|
+
}
|
|
2023
|
+
if (envelope.getStringIndexType() || envelope.getNumberIndexType()) {
|
|
2024
|
+
diagnostics.push("return envelope must not declare an index signature");
|
|
2025
|
+
}
|
|
2026
|
+
const envelopeProperties = envelope.getProperties();
|
|
2027
|
+
const envelopeNames = envelopeProperties.map((property) => property.getName()).sort();
|
|
2028
|
+
if (envelopeNames.join(",") !== "receipt,result") {
|
|
2029
|
+
diagnostics.push(
|
|
2030
|
+
`return envelope must have exactly the required receipt and result fields. Found ${envelopeNames.join(", ") || "none"}`
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
const receiptProperty = envelope.getProperty("receipt");
|
|
2034
|
+
const resultProperty = envelope.getProperty("result");
|
|
2035
|
+
if (receiptProperty?.isOptional()) diagnostics.push("receipt must be required");
|
|
2036
|
+
if (resultProperty?.isOptional()) diagnostics.push("result must be required");
|
|
2037
|
+
const location = fn.getTypeResolutionNode();
|
|
2038
|
+
if (receiptProperty) {
|
|
2039
|
+
const receiptType = receiptProperty.getTypeAtLocation(location);
|
|
2040
|
+
const failure = wireTypeFailure(receiptType);
|
|
2041
|
+
if (failure !== void 0) {
|
|
2042
|
+
diagnostics.push(
|
|
2043
|
+
`receipt must be a durable wire value, got ${describeType(receiptType, fn)} (${failure})`
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (!resultProperty) {
|
|
2048
|
+
const expectedNames2 = Object.entries(outputs).filter(([, output]) => output.scope === void 0).map(([name]) => name).sort();
|
|
2049
|
+
const plainResultNames = envelopeProperties.map((property) => property.getName()).sort();
|
|
2050
|
+
const missing = expectedNames2.filter((name) => !plainResultNames.includes(name));
|
|
2051
|
+
const extra = plainResultNames.filter((name) => !expectedNames2.includes(name));
|
|
2052
|
+
diagnostics.push(
|
|
2053
|
+
[
|
|
2054
|
+
"result fields must exactly match the node outputs",
|
|
2055
|
+
missing.length > 0 ? `missing ${missing.join(", ")}` : void 0,
|
|
2056
|
+
extra.length > 0 ? `unexpected ${extra.join(", ")}` : void 0
|
|
2057
|
+
].filter(Boolean).join("; ")
|
|
2058
|
+
);
|
|
2059
|
+
return diagnostics;
|
|
2060
|
+
}
|
|
2061
|
+
const resultType = resultProperty.getTypeAtLocation(location);
|
|
2062
|
+
if (!resultType.isObject() || resultType.isArray() || resultType.isReadonlyArray()) {
|
|
2063
|
+
diagnostics.push(
|
|
2064
|
+
`result must be an object matching the node outputs, got ${describeType(resultType, fn)}`
|
|
2065
|
+
);
|
|
2066
|
+
return diagnostics;
|
|
2067
|
+
}
|
|
2068
|
+
const resultFailure = wireTypeFailure(resultType);
|
|
2069
|
+
if (resultFailure !== void 0) {
|
|
2070
|
+
diagnostics.push(
|
|
2071
|
+
`result must be a durable wire value, got ${describeType(resultType, fn)} (${resultFailure})`
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const expectedNames = Object.entries(outputs).filter(([, output]) => output.scope === void 0).map(([name]) => name).sort();
|
|
2075
|
+
const actualNames = resultType.getProperties().map((property) => property.getName()).sort();
|
|
2076
|
+
if (actualNames.join(",") !== expectedNames.join(",")) {
|
|
2077
|
+
const missing = expectedNames.filter((name) => !actualNames.includes(name));
|
|
2078
|
+
const extra = actualNames.filter((name) => !expectedNames.includes(name));
|
|
2079
|
+
diagnostics.push(
|
|
2080
|
+
[
|
|
2081
|
+
"result fields must exactly match the node outputs",
|
|
2082
|
+
missing.length > 0 ? `missing ${missing.join(", ")}` : void 0,
|
|
2083
|
+
extra.length > 0 ? `unexpected ${extra.join(", ")}` : void 0
|
|
2084
|
+
].filter(Boolean).join("; ")
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
for (const [name, output] of Object.entries(outputs)) {
|
|
2088
|
+
if (output.scope !== void 0) continue;
|
|
2089
|
+
const property = resultType.getProperty(name);
|
|
2090
|
+
if (!property) continue;
|
|
2091
|
+
if (property.isOptional()) {
|
|
2092
|
+
diagnostics.push(`result.${name} must be required`);
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
const propertyType = property.getTypeAtLocation(location);
|
|
2096
|
+
if (!matchesPortType(propertyType, output.dataType)) {
|
|
2097
|
+
diagnostics.push(
|
|
2098
|
+
`result.${name} is ${describeType(propertyType, fn)}, incompatible with ${output.dataType}`
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return diagnostics;
|
|
2103
|
+
}
|
|
2104
|
+
function analyzeDurableEffectContract(fn, inputs, outputs) {
|
|
2105
|
+
const diagnostics = [];
|
|
2106
|
+
const parameters = fn.getParameters();
|
|
2107
|
+
const operationKey = parameters.at(-1);
|
|
2108
|
+
if (!operationKey) {
|
|
2109
|
+
diagnostics.push("callable must accept a required final operationKey: string parameter");
|
|
2110
|
+
} else {
|
|
2111
|
+
if (operationKey.isOptional() || operationKey.hasInitializer() || operationKey.isRestParameter()) {
|
|
2112
|
+
diagnostics.push("final operationKey parameter must be required and non-rest");
|
|
2113
|
+
}
|
|
2114
|
+
const operationKeyType = operationKey.getType();
|
|
2115
|
+
if (!operationKeyType.isString() || operationKeyType.isStringLiteral()) {
|
|
2116
|
+
diagnostics.push(
|
|
2117
|
+
`final operationKey parameter must be string, got ${describeType(operationKeyType, fn)}`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
if (Object.hasOwn(inputs, operationKey.getName())) {
|
|
2121
|
+
diagnostics.push(
|
|
2122
|
+
"callable must accept an injected final operationKey: string after every declared node input"
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
const declaredReturn = fn.getReturnType();
|
|
2127
|
+
const resolvedReturn = declaredReturn.getAwaitedType() ?? declaredReturn;
|
|
2128
|
+
const returnMembers = resolvedReturn.isUnion() ? resolvedReturn.getUnionTypes() : [resolvedReturn];
|
|
2129
|
+
for (const returnMember of returnMembers) {
|
|
2130
|
+
diagnostics.push(...validateEnvelopeMember(returnMember, outputs, fn));
|
|
2131
|
+
}
|
|
2132
|
+
return { valid: diagnostics.length === 0, diagnostics };
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/parser/node-inference.ts
|
|
2136
|
+
function returnsPromise(fn) {
|
|
2137
|
+
return fn.isAsync() || fn.getReturnType().getText().startsWith("Promise<");
|
|
2138
|
+
}
|
|
2139
|
+
function extractNodeTypes(sourceFile, warnings, tagRegistry) {
|
|
2140
|
+
const nodeTypes = [];
|
|
2141
|
+
warnings.push(...nestedAnnotationWarnings(sourceFile));
|
|
2142
|
+
extractFunctionLikes(sourceFile).forEach((fn) => {
|
|
2143
|
+
const config = jsdocParser.parseNodeType(fn, warnings, tagRegistry);
|
|
2144
|
+
if (!config) {
|
|
2145
|
+
const jsdocText = fn.getJsDocs().map((d) => d.getFullText()).join("");
|
|
2146
|
+
if (jsdocText.includes("@flowWeaver nodeType")) {
|
|
2147
|
+
warnings.push(
|
|
2148
|
+
`Function "${fn.getName() || "anonymous"}" has @flowWeaver annotation but could not be parsed. Check for special characters (---) or malformed JSDoc syntax.`
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
const functionName = fn.getName() || "anonymous";
|
|
2154
|
+
const nodeTypeName = config.name || functionName;
|
|
2155
|
+
const durableGate = durableGateKind(fn);
|
|
2156
|
+
const durableEffect = hasJsDocTag(fn, "durableEffect");
|
|
2157
|
+
const durablePure = hasJsDocTag(fn, "durablePure");
|
|
2158
|
+
const builtIn = BUILT_IN_NODE_TYPES.find((nt) => nt.functionName === functionName);
|
|
2159
|
+
const paramTypes = builtIn ? fn.getParameters().map((p) => p.getTypeNode()?.getText()) : [];
|
|
2160
|
+
const receivesAbortSignal = builtIn?.receivesAbortSignal === true && paramTypes.includes("AbortSignal");
|
|
2161
|
+
const receivesRuntime = builtIn?.receivesRuntime === true && paramTypes.includes("NodeExecutionRuntime");
|
|
2162
|
+
const inputs = {};
|
|
2163
|
+
if (config.inputs) {
|
|
2164
|
+
for (const [portName, portDef] of Object.entries(config.inputs)) {
|
|
2165
|
+
inputs[portName] = {
|
|
2166
|
+
dataType: portDef.type,
|
|
2167
|
+
default: portDef.defaultValue,
|
|
2168
|
+
optional: portDef.optional,
|
|
2169
|
+
label: portDef.label,
|
|
2170
|
+
expression: portDef.expression,
|
|
2171
|
+
...portDef.scope && { scope: portDef.scope },
|
|
2172
|
+
...portDef.hidden && { hidden: portDef.hidden },
|
|
2173
|
+
...portDef.metadata && { metadata: portDef.metadata },
|
|
2174
|
+
...portDef.tsType && { tsType: portDef.tsType },
|
|
2175
|
+
// The fan-in strategy is what lets the validator accept several
|
|
2176
|
+
// connections into this port and tells the generator how to combine
|
|
2177
|
+
// them. Dropping it here silently turned every strategy into FIRST.
|
|
2178
|
+
...portDef.mergeStrategy && { mergeStrategy: portDef.mergeStrategy }
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
const outputs = {};
|
|
2183
|
+
if (config.outputs) {
|
|
2184
|
+
for (const [portName, portDef] of Object.entries(config.outputs)) {
|
|
2185
|
+
outputs[portName] = {
|
|
2186
|
+
dataType: portDef.type,
|
|
2187
|
+
label: portDef.label,
|
|
2188
|
+
...portDef.scope && { scope: portDef.scope },
|
|
2189
|
+
...portDef.hidden && { hidden: portDef.hidden },
|
|
2190
|
+
...portDef.metadata && { metadata: portDef.metadata },
|
|
2191
|
+
...portDef.tsType && { tsType: portDef.tsType }
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const isStub = fn.isAmbient?.() ?? false;
|
|
2196
|
+
if (isStub) {
|
|
2197
|
+
config.expression = true;
|
|
2198
|
+
}
|
|
2199
|
+
if (config.expression) {
|
|
2200
|
+
const inferred = inferNodeTypeFromFunction(
|
|
2201
|
+
fn,
|
|
2202
|
+
nodeTypeName,
|
|
2203
|
+
fn.getSourceFile().getFilePath()
|
|
2204
|
+
);
|
|
2205
|
+
const reconcile = (explicit, inferredPorts, isControl) => {
|
|
2206
|
+
const explicitData = Object.entries(explicit).filter(([name]) => !isControl(name));
|
|
2207
|
+
const inferredData = Object.entries(inferredPorts).filter(([name]) => !isControl(name));
|
|
2208
|
+
if (explicitData.length === 0) return Object.fromEntries(inferredData);
|
|
2209
|
+
const inferredNames = new Set(inferredData.map(([name]) => name));
|
|
2210
|
+
const isPartial = explicitData.length < inferredData.length && explicitData.every(([name]) => inferredNames.has(name));
|
|
2211
|
+
if (!isPartial) return Object.fromEntries(explicitData);
|
|
2212
|
+
const explicitByName = new Map(explicitData);
|
|
2213
|
+
return Object.fromEntries(
|
|
2214
|
+
inferredData.map(([name, port]) => {
|
|
2215
|
+
const override = explicitByName.get(name);
|
|
2216
|
+
if (!override) return [name, port];
|
|
2217
|
+
const refined = { ...port };
|
|
2218
|
+
for (const [key, value] of Object.entries(override)) {
|
|
2219
|
+
if (value === void 0) continue;
|
|
2220
|
+
if (key === "dataType" && value === "ANY") continue;
|
|
2221
|
+
if (key === "label" && typeof value === "string" && value.length === 0) continue;
|
|
2222
|
+
refined[key] = value;
|
|
2223
|
+
}
|
|
2224
|
+
return [name, refined];
|
|
2225
|
+
})
|
|
2226
|
+
);
|
|
2227
|
+
};
|
|
2228
|
+
const reconciledInputs = reconcile(inputs, inferred.inputs, (n) => n === "execute");
|
|
2229
|
+
const reconciledOutputs = reconcile(outputs, inferred.outputs, (n) => n === "onSuccess" || n === "onFailure");
|
|
2230
|
+
for (const key of Object.keys(inputs)) if (key !== "execute") delete inputs[key];
|
|
2231
|
+
for (const key of Object.keys(outputs)) if (key !== "onSuccess" && key !== "onFailure") delete outputs[key];
|
|
2232
|
+
Object.assign(inputs, reconciledInputs);
|
|
2233
|
+
Object.assign(outputs, reconciledOutputs);
|
|
2234
|
+
}
|
|
2235
|
+
inputs.execute = {
|
|
2236
|
+
label: "Execute",
|
|
2237
|
+
// Default label
|
|
2238
|
+
...inputs.execute,
|
|
2239
|
+
// User can override label
|
|
2240
|
+
dataType: "STEP"
|
|
2241
|
+
// But dataType is mandatory
|
|
2242
|
+
};
|
|
2243
|
+
outputs.onSuccess = {
|
|
2244
|
+
label: "On Success",
|
|
2245
|
+
// Default label
|
|
2246
|
+
...outputs.onSuccess,
|
|
2247
|
+
// User can override label
|
|
2248
|
+
dataType: "STEP",
|
|
2249
|
+
// But dataType is mandatory
|
|
2250
|
+
isControlFlow: true
|
|
2251
|
+
// Always a control flow port
|
|
2252
|
+
};
|
|
2253
|
+
outputs.onFailure = {
|
|
2254
|
+
label: "On Failure",
|
|
2255
|
+
// Default label
|
|
2256
|
+
...outputs.onFailure,
|
|
2257
|
+
// User can override label
|
|
2258
|
+
dataType: "STEP",
|
|
2259
|
+
// But dataType is mandatory
|
|
2260
|
+
failure: true,
|
|
2261
|
+
// Always a failure port
|
|
2262
|
+
isControlFlow: true
|
|
2263
|
+
// Always a control flow port
|
|
2264
|
+
};
|
|
2265
|
+
assignImplicitPortOrders(inputs);
|
|
2266
|
+
assignImplicitPortOrders(outputs);
|
|
2267
|
+
const jsDocs = fn.getJsDocs();
|
|
2268
|
+
const jsDocText = jsDocs.map((doc) => doc.getText()).join("\n");
|
|
2269
|
+
const functionText = isStub ? void 0 : jsDocText ? `${jsDocText}
|
|
2270
|
+
${fn.getText()}` : fn.getText();
|
|
2271
|
+
const durableEffectContract = durableEffect ? analyzeDurableEffectContract(fn, inputs, outputs) : void 0;
|
|
2272
|
+
const isAsync = returnsPromise(fn);
|
|
2273
|
+
let defaultConfig = void 0;
|
|
2274
|
+
if (config.defaultConfig) {
|
|
2275
|
+
defaultConfig = {
|
|
2276
|
+
label: config.defaultConfig.label,
|
|
2277
|
+
description: config.defaultConfig.description,
|
|
2278
|
+
pullExecution: config.defaultConfig.pullExecution
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
const portScopes = /* @__PURE__ */ new Set();
|
|
2282
|
+
Object.values(inputs).forEach((port) => {
|
|
2283
|
+
if (port.scope) portScopes.add(port.scope);
|
|
2284
|
+
});
|
|
2285
|
+
Object.values(outputs).forEach((port) => {
|
|
2286
|
+
if (port.scope) portScopes.add(port.scope);
|
|
2287
|
+
});
|
|
2288
|
+
let scopes;
|
|
2289
|
+
if (config.scope) {
|
|
2290
|
+
scopes = [config.scope];
|
|
2291
|
+
} else if (portScopes.size > 0) {
|
|
2292
|
+
const orderedScopes = [];
|
|
2293
|
+
try {
|
|
2294
|
+
const params = fn.getParameters();
|
|
2295
|
+
for (const param of params) {
|
|
2296
|
+
const paramName = param.getName();
|
|
2297
|
+
if (portScopes.has(paramName)) {
|
|
2298
|
+
orderedScopes.push(paramName);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
} catch {
|
|
2302
|
+
}
|
|
2303
|
+
for (const scope of portScopes) {
|
|
2304
|
+
if (!orderedScopes.includes(scope)) {
|
|
2305
|
+
orderedScopes.push(scope);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
scopes = orderedScopes;
|
|
2309
|
+
}
|
|
2310
|
+
nodeTypes.push({
|
|
2311
|
+
type: "NodeType",
|
|
2312
|
+
name: nodeTypeName,
|
|
2313
|
+
functionName,
|
|
2314
|
+
variant: isStub ? "STUB" : "FUNCTION",
|
|
2315
|
+
inputs,
|
|
2316
|
+
outputs,
|
|
2317
|
+
hasSuccessPort: RESERVED_PORT_NAMES.ON_SUCCESS in outputs,
|
|
2318
|
+
hasFailurePort: RESERVED_PORT_NAMES.ON_FAILURE in outputs,
|
|
2319
|
+
isAsync,
|
|
2320
|
+
functionText,
|
|
2321
|
+
...durableGate && { durableGate },
|
|
2322
|
+
...durableEffect && { durableEffect: true },
|
|
2323
|
+
...durableEffectContract && { durableEffectContract },
|
|
2324
|
+
...durablePure && { durablePure: true },
|
|
2325
|
+
...receivesAbortSignal && { receivesAbortSignal: true },
|
|
2326
|
+
...receivesRuntime && { receivesRuntime: true },
|
|
2327
|
+
...config.resilience && { resilience: config.resilience },
|
|
2328
|
+
executeWhen: config.executeWhen || EXECUTION_STRATEGIES.CONJUNCTION,
|
|
2329
|
+
defaultConfig,
|
|
2330
|
+
scope: config.scope,
|
|
2331
|
+
scopes,
|
|
2332
|
+
...config.expression && { expression: true },
|
|
2333
|
+
...fn.getDeclarationKind?.() && { declarationKind: fn.getDeclarationKind() },
|
|
2334
|
+
label: config.label,
|
|
2335
|
+
description: config.description,
|
|
2336
|
+
visuals: config.color || config.icon || config.tags ? {
|
|
2337
|
+
color: config.color,
|
|
2338
|
+
icon: config.icon,
|
|
2339
|
+
tags: config.tags
|
|
2340
|
+
} : void 0,
|
|
2341
|
+
...config.deploy && { deploy: config.deploy },
|
|
2342
|
+
sourceLocation: {
|
|
2343
|
+
file: sourceFile.getFilePath(),
|
|
2344
|
+
line: fn.getStartLineNumber(false),
|
|
2345
|
+
column: 0
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
});
|
|
2349
|
+
return nodeTypes;
|
|
2350
|
+
}
|
|
2351
|
+
function inferNodeTypeFromFunction(fn, name, filePath) {
|
|
2352
|
+
const durableGate = durableGateKind(fn);
|
|
2353
|
+
const durableEffect = hasJsDocTag(fn, "durableEffect");
|
|
2354
|
+
const durablePure = hasJsDocTag(fn, "durablePure");
|
|
2355
|
+
const inputs = {};
|
|
2356
|
+
const params = fn.getParameters();
|
|
2357
|
+
const firstParamIsExecute = params.length > 0 && params[0].getName() === "execute";
|
|
2358
|
+
const authoredInputParams = durableEffect ? params.slice(0, -1) : params;
|
|
2359
|
+
for (const param of authoredInputParams) {
|
|
2360
|
+
const paramName = param.getName();
|
|
2361
|
+
const optional = param.isOptional() || param.hasInitializer();
|
|
2362
|
+
const rawTsType = param.getType().getText(param);
|
|
2363
|
+
const tsType = optional ? stripOptionalUndefined(rawTsType) : rawTsType;
|
|
2364
|
+
const dataType = inferDataTypeFromTS(tsType);
|
|
2365
|
+
inputs[paramName] = {
|
|
2366
|
+
dataType,
|
|
2367
|
+
optional: optional || void 0,
|
|
2368
|
+
label: capitalize(paramName),
|
|
2369
|
+
tsType
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
const outputs = {};
|
|
2373
|
+
let returnType = fn.getReturnType();
|
|
2374
|
+
const returnTypeText = returnType.getText();
|
|
2375
|
+
if (returnTypeText.startsWith("Promise<")) {
|
|
2376
|
+
const typeArgs = returnType.getTypeArguments();
|
|
2377
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
2378
|
+
returnType = typeArgs[0];
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
if (durableEffect) {
|
|
2382
|
+
const effectResult = returnType.getProperty("result");
|
|
2383
|
+
if (effectResult) {
|
|
2384
|
+
returnType = effectResult.getTypeAtLocation(fn.getTypeResolutionNode());
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
const unwrappedText = returnType.getText();
|
|
2388
|
+
if (unwrappedText !== "void" && unwrappedText !== "undefined") {
|
|
2389
|
+
const primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "unknown", "never"]);
|
|
2390
|
+
const isPrimitive = primitiveTypes.has(unwrappedText);
|
|
2391
|
+
const isArray = unwrappedText.endsWith("[]") || unwrappedText.startsWith("Array<");
|
|
2392
|
+
const properties = returnType.getProperties();
|
|
2393
|
+
const isObjectLike = !isPrimitive && !isArray && returnType.isObject() && properties.length > 0;
|
|
2394
|
+
if (isObjectLike) {
|
|
2395
|
+
for (const prop of properties) {
|
|
2396
|
+
const propName = prop.getName();
|
|
2397
|
+
if (propName === "onSuccess" || propName === "onFailure") continue;
|
|
2398
|
+
const propType = prop.getTypeAtLocation(fn.getTypeResolutionNode());
|
|
2399
|
+
const propTypeText = propType.getText();
|
|
2400
|
+
const dataType = inferDataTypeFromTS(propTypeText);
|
|
2401
|
+
outputs[propName] = {
|
|
2402
|
+
dataType,
|
|
2403
|
+
label: capitalize(propName),
|
|
2404
|
+
tsType: propTypeText
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
} else {
|
|
2408
|
+
const dataType = inferDataTypeFromTS(unwrappedText);
|
|
2409
|
+
outputs.result = {
|
|
2410
|
+
dataType,
|
|
2411
|
+
label: "Result",
|
|
2412
|
+
tsType: unwrappedText
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
inputs.execute = { dataType: "STEP", label: "Execute" };
|
|
2417
|
+
outputs.onSuccess = { dataType: "STEP", label: "On Success", isControlFlow: true };
|
|
2418
|
+
outputs.onFailure = {
|
|
2419
|
+
dataType: "STEP",
|
|
2420
|
+
label: "On Failure",
|
|
2421
|
+
failure: true,
|
|
2422
|
+
isControlFlow: true
|
|
2423
|
+
};
|
|
2424
|
+
assignImplicitPortOrders(inputs);
|
|
2425
|
+
assignImplicitPortOrders(outputs);
|
|
2426
|
+
const jsDocs = fn.getJsDocs();
|
|
2427
|
+
const jsDocText = jsDocs.map((doc) => doc.getText()).join("\n");
|
|
2428
|
+
const functionText = jsDocText ? `${jsDocText}
|
|
2429
|
+
${fn.getText()}` : fn.getText();
|
|
2430
|
+
const durableEffectContract = durableEffect ? analyzeDurableEffectContract(fn, inputs, outputs) : void 0;
|
|
2431
|
+
return {
|
|
2432
|
+
type: "NodeType",
|
|
2433
|
+
name,
|
|
2434
|
+
functionName: name,
|
|
2435
|
+
variant: "FUNCTION",
|
|
2436
|
+
inputs,
|
|
2437
|
+
outputs,
|
|
2438
|
+
hasSuccessPort: true,
|
|
2439
|
+
hasFailurePort: true,
|
|
2440
|
+
isAsync: returnsPromise(fn),
|
|
2441
|
+
executeWhen: EXECUTION_STRATEGIES.CONJUNCTION,
|
|
2442
|
+
expression: !firstParamIsExecute,
|
|
2443
|
+
// Expression only if original function lacks execute as first param
|
|
2444
|
+
inferred: true,
|
|
2445
|
+
functionText,
|
|
2446
|
+
...durableGate && { durableGate },
|
|
2447
|
+
...durableEffect && { durableEffect: true },
|
|
2448
|
+
...durableEffectContract && { durableEffectContract },
|
|
2449
|
+
...durablePure && { durablePure: true },
|
|
2450
|
+
...fn.getDeclarationKind?.() && {
|
|
2451
|
+
declarationKind: fn.getDeclarationKind()
|
|
2452
|
+
},
|
|
2453
|
+
sourceLocation: {
|
|
2454
|
+
file: filePath,
|
|
2455
|
+
line: fn.getStartLineNumber(false),
|
|
2456
|
+
column: 0
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
function inferAllUnannotatedFunctions(sourceFile, existingNodeTypes) {
|
|
2461
|
+
const allFunctions = extractFunctionLikes(sourceFile);
|
|
2462
|
+
const existingNames = /* @__PURE__ */ new Set();
|
|
2463
|
+
for (const nt of existingNodeTypes) {
|
|
2464
|
+
existingNames.add(nt.name);
|
|
2465
|
+
existingNames.add(nt.functionName);
|
|
2466
|
+
}
|
|
2467
|
+
const inferred = [];
|
|
2468
|
+
for (const fn of allFunctions) {
|
|
2469
|
+
const fnName = fn.getName();
|
|
2470
|
+
if (!fnName) continue;
|
|
2471
|
+
if (existingNames.has(fnName)) continue;
|
|
2472
|
+
if (hasFlowWeaverAnnotation(fn)) continue;
|
|
2473
|
+
inferred.push(inferNodeTypeFromFunction(fn, fnName, sourceFile.getFilePath()));
|
|
2474
|
+
existingNames.add(fnName);
|
|
2475
|
+
}
|
|
2476
|
+
return inferred;
|
|
2477
|
+
}
|
|
2478
|
+
function inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes, localNodeTypes, warnings) {
|
|
2479
|
+
const allFunctions = extractFunctionLikes(sourceFile);
|
|
2480
|
+
const referencedTypes = /* @__PURE__ */ new Set();
|
|
2481
|
+
for (const fn of allFunctions) {
|
|
2482
|
+
const config = jsdocParser.parseWorkflow(fn, []);
|
|
2483
|
+
if (!config) continue;
|
|
2484
|
+
for (const inst of config.instances || []) {
|
|
2485
|
+
referencedTypes.add(inst.type);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
const existingNames = /* @__PURE__ */ new Set();
|
|
2489
|
+
for (const nt of existingNodeTypes) {
|
|
2490
|
+
existingNames.add(nt.name);
|
|
2491
|
+
existingNames.add(nt.functionName);
|
|
2492
|
+
}
|
|
2493
|
+
const unresolvedTypes = /* @__PURE__ */ new Set();
|
|
2494
|
+
for (const typeName of referencedTypes) {
|
|
2495
|
+
if (!existingNames.has(typeName)) {
|
|
2496
|
+
unresolvedTypes.add(typeName);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
if (unresolvedTypes.size === 0) return [];
|
|
2500
|
+
const inferredNodeTypes = [];
|
|
2501
|
+
const alreadyInferred = /* @__PURE__ */ new Set();
|
|
2502
|
+
const builtInByName = new Map(BUILT_IN_NODE_TYPES.map((nt) => [nt.name, nt]));
|
|
2503
|
+
const annotatedNames = new Set(localNodeTypes.map((nt) => nt.functionName));
|
|
2504
|
+
for (const unresolvedType of unresolvedTypes) {
|
|
2505
|
+
if (alreadyInferred.has(unresolvedType)) continue;
|
|
2506
|
+
const builtIn = builtInByName.get(unresolvedType);
|
|
2507
|
+
if (builtIn) {
|
|
2508
|
+
inferredNodeTypes.push(builtIn);
|
|
2509
|
+
alreadyInferred.add(unresolvedType);
|
|
2510
|
+
if (!annotatedNames.has(unresolvedType)) {
|
|
2511
|
+
const shadowFn = allFunctions.find((fn) => fn.getName() === unresolvedType && !hasFlowWeaverAnnotation(fn));
|
|
2512
|
+
if (shadowFn) {
|
|
2513
|
+
warnings.push(
|
|
2514
|
+
`Function '${unresolvedType}' exists in this file but is not annotated with @flowWeaver nodeType. The built-in '${unresolvedType}' will be used instead. Add @flowWeaver nodeType to use your version.`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
const matchedFn = allFunctions.find((fn) => {
|
|
2521
|
+
if (fn.getName() !== unresolvedType) return false;
|
|
2522
|
+
return !hasFlowWeaverAnnotation(fn);
|
|
2523
|
+
});
|
|
2524
|
+
if (!matchedFn) continue;
|
|
2525
|
+
inferredNodeTypes.push(
|
|
2526
|
+
inferNodeTypeFromFunction(matchedFn, unresolvedType, sourceFile.getFilePath())
|
|
2527
|
+
);
|
|
2528
|
+
alreadyInferred.add(unresolvedType);
|
|
2529
|
+
}
|
|
2530
|
+
return inferredNodeTypes;
|
|
2531
|
+
}
|
|
2532
|
+
function hasFlowWeaverAnnotation(fn) {
|
|
2533
|
+
const validTypes = /* @__PURE__ */ new Set(["nodeType", "node", "workflow"]);
|
|
2534
|
+
return fn.getJsDocs().some(
|
|
2535
|
+
(doc) => doc.getTags().some((t) => {
|
|
2536
|
+
if (t.getTagName() !== "flowWeaver") return false;
|
|
2537
|
+
const comment = t.getCommentText?.()?.trim() || "";
|
|
2538
|
+
return validTypes.has(comment.split(/\s/)[0]);
|
|
2539
|
+
})
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// src/parser/module-resolution.ts
|
|
2544
|
+
import * as ts from "typescript";
|
|
2545
|
+
import * as path2 from "node:path";
|
|
2546
|
+
import * as fs from "node:fs";
|
|
2547
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"]);
|
|
2548
|
+
function resolveModulePath(moduleSpecifier, currentDir, warnings) {
|
|
2549
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
2550
|
+
const hasExtension = extensions.some((ext) => moduleSpecifier.endsWith(ext));
|
|
2551
|
+
if (hasExtension) {
|
|
2552
|
+
const fullPath = path2.resolve(currentDir, moduleSpecifier);
|
|
2553
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
2554
|
+
if (moduleSpecifier.endsWith(".js")) {
|
|
2555
|
+
const tsPath = fullPath.replace(/\.js$/, ".ts");
|
|
2556
|
+
if (fs.existsSync(tsPath)) return tsPath;
|
|
2557
|
+
const tsxPath = fullPath.replace(/\.js$/, ".tsx");
|
|
2558
|
+
if (fs.existsSync(tsxPath)) return tsxPath;
|
|
2559
|
+
} else if (moduleSpecifier.endsWith(".jsx")) {
|
|
2560
|
+
const tsxPath = fullPath.replace(/\.jsx$/, ".tsx");
|
|
2561
|
+
if (fs.existsSync(tsxPath)) return tsxPath;
|
|
2562
|
+
}
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
for (const ext of extensions) {
|
|
2566
|
+
const fullPath = path2.resolve(currentDir, moduleSpecifier + ext);
|
|
2567
|
+
if (fs.existsSync(fullPath)) {
|
|
2568
|
+
return fullPath;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
const dirPath = path2.resolve(currentDir, moduleSpecifier);
|
|
2572
|
+
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
|
2573
|
+
const pkgPath = path2.join(dirPath, "package.json");
|
|
2574
|
+
if (fs.existsSync(pkgPath)) {
|
|
2575
|
+
try {
|
|
2576
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
2577
|
+
if (pkg.main) {
|
|
2578
|
+
const mainPath = path2.resolve(dirPath, pkg.main);
|
|
2579
|
+
if (fs.existsSync(mainPath)) {
|
|
2580
|
+
return mainPath;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
} catch (e) {
|
|
2584
|
+
warnings?.push(`Could not read ${pkgPath} while resolving "${moduleSpecifier}": ${getErrorMessage(e)}`);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
for (const ext of extensions) {
|
|
2588
|
+
const indexPath = path2.join(dirPath, `index${ext}`);
|
|
2589
|
+
if (fs.existsSync(indexPath)) {
|
|
2590
|
+
return indexPath;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
function resolveReExportedDts(baseDir, spec) {
|
|
2597
|
+
const noExt = spec.replace(/\.(js|mjs|cjs|jsx|ts|tsx)$/, "");
|
|
2598
|
+
const candidates = [
|
|
2599
|
+
path2.resolve(baseDir, `${noExt}.d.ts`),
|
|
2600
|
+
path2.resolve(baseDir, noExt, "index.d.ts"),
|
|
2601
|
+
path2.resolve(baseDir, spec)
|
|
2602
|
+
// already a .d.ts path
|
|
2603
|
+
];
|
|
2604
|
+
for (const c of candidates) {
|
|
2605
|
+
if (c.endsWith(".d.ts") && fs.existsSync(c)) return c;
|
|
2606
|
+
}
|
|
2607
|
+
return null;
|
|
2608
|
+
}
|
|
2609
|
+
function preloadSourceOverrides(project, importer, source, importResolver, sourceLoader, seen) {
|
|
2610
|
+
const parsed = ts.createSourceFile(importer, source, ts.ScriptTarget.Latest, true);
|
|
2611
|
+
for (const statement of parsed.statements) {
|
|
2612
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
const specifier = statement.moduleSpecifier.text;
|
|
2616
|
+
if (!specifier.startsWith(".")) continue;
|
|
2617
|
+
const resolved = importResolver?.(specifier, importer) ?? resolveModulePath(specifier, path2.dirname(importer));
|
|
2618
|
+
if (resolved === void 0 || resolved === null || seen.has(resolved)) continue;
|
|
2619
|
+
const override = sourceLoader(resolved);
|
|
2620
|
+
if (override === void 0) continue;
|
|
2621
|
+
seen.add(resolved);
|
|
2622
|
+
preloadSourceOverrides(project, resolved, override, importResolver, sourceLoader, seen);
|
|
2623
|
+
project.createSourceFile(resolved, override, { overwrite: true });
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// src/parser/import-resolution.ts
|
|
2628
|
+
import * as path3 from "node:path";
|
|
2629
|
+
import * as fs2 from "node:fs";
|
|
2630
|
+
|
|
2631
|
+
// src/parser/macro-expansion.ts
|
|
2632
|
+
function expandMapMacro(mapConfig, instances, connections, scopes, availableNodeTypes, macros, errors, _warnings) {
|
|
2633
|
+
const { instanceId, childId, sourceNode, sourcePort } = mapConfig;
|
|
2634
|
+
const scopeName = "iterate";
|
|
2635
|
+
const childInstance = instances.find((inst) => inst.id === childId);
|
|
2636
|
+
if (!childInstance) {
|
|
2637
|
+
errors.push(
|
|
2638
|
+
`@map "${instanceId}": child node "${childId}" not found. Declare it with @node before using @map.`
|
|
2639
|
+
);
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
const childNodeType = availableNodeTypes.find(
|
|
2643
|
+
(nt) => nt.name === childInstance.nodeType || nt.functionName === childInstance.nodeType
|
|
2644
|
+
);
|
|
2645
|
+
if (!childNodeType) {
|
|
2646
|
+
errors.push(
|
|
2647
|
+
`@map "${instanceId}": node type "${childInstance.nodeType}" for child "${childId}" not found.`
|
|
2648
|
+
);
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
let inputPort = mapConfig.inputPort;
|
|
2652
|
+
let outputPort = mapConfig.outputPort;
|
|
2653
|
+
if (!inputPort) {
|
|
2654
|
+
const dataInputs = Object.entries(childNodeType.inputs).filter(
|
|
2655
|
+
([name, def]) => name !== "execute" && def.dataType !== "STEP"
|
|
2656
|
+
);
|
|
2657
|
+
if (dataInputs.length === 0) {
|
|
2658
|
+
errors.push(
|
|
2659
|
+
`@map "${instanceId}": child node "${childId}" has no data input ports to receive items.`
|
|
2660
|
+
);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
inputPort = dataInputs[0][0];
|
|
2664
|
+
}
|
|
2665
|
+
if (!outputPort) {
|
|
2666
|
+
const dataOutputs = Object.entries(childNodeType.outputs).filter(
|
|
2667
|
+
([name, def]) => name !== "onSuccess" && name !== "onFailure" && def.dataType !== "STEP" && !def.isControlFlow && !def.failure
|
|
2668
|
+
);
|
|
2669
|
+
if (dataOutputs.length === 0) {
|
|
2670
|
+
errors.push(
|
|
2671
|
+
`@map "${instanceId}": child node "${childId}" has no data output ports for results.`
|
|
2672
|
+
);
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
outputPort = dataOutputs[0][0];
|
|
2676
|
+
}
|
|
2677
|
+
const childInputDef = childNodeType.inputs[inputPort];
|
|
2678
|
+
const childOutputDef = childNodeType.outputs[outputPort];
|
|
2679
|
+
const syntheticTypeName = `__map_${instanceId}__`;
|
|
2680
|
+
const syntheticNodeType = {
|
|
2681
|
+
type: "NodeType",
|
|
2682
|
+
name: syntheticTypeName,
|
|
2683
|
+
functionName: syntheticTypeName,
|
|
2684
|
+
variant: "MAP_ITERATOR",
|
|
2685
|
+
isAsync: true,
|
|
2686
|
+
executeWhen: "CONJUNCTION",
|
|
2687
|
+
hasSuccessPort: true,
|
|
2688
|
+
hasFailurePort: true,
|
|
2689
|
+
scope: scopeName,
|
|
2690
|
+
scopes: [scopeName],
|
|
2691
|
+
inputs: {
|
|
2692
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
2693
|
+
items: {
|
|
2694
|
+
dataType: "ARRAY",
|
|
2695
|
+
label: "Items",
|
|
2696
|
+
tsType: childInputDef?.tsType ? `(${childInputDef.tsType})[]` : "unknown[]"
|
|
2697
|
+
},
|
|
2698
|
+
// Scoped INPUT ports (receive from children)
|
|
2699
|
+
success: { dataType: "STEP", scope: scopeName },
|
|
2700
|
+
failure: { dataType: "STEP", scope: scopeName },
|
|
2701
|
+
processed: {
|
|
2702
|
+
dataType: childOutputDef?.dataType || "ANY",
|
|
2703
|
+
scope: scopeName,
|
|
2704
|
+
...childOutputDef?.tsType && { tsType: childOutputDef.tsType }
|
|
2705
|
+
}
|
|
2706
|
+
},
|
|
2707
|
+
outputs: {
|
|
2708
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
2709
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
2710
|
+
results: {
|
|
2711
|
+
dataType: "ARRAY",
|
|
2712
|
+
label: "Results",
|
|
2713
|
+
tsType: childOutputDef?.tsType ? `(${childOutputDef.tsType})[]` : "unknown[]"
|
|
2714
|
+
},
|
|
2715
|
+
// Scoped OUTPUT ports (send to children)
|
|
2716
|
+
start: { dataType: "STEP", scope: scopeName },
|
|
2717
|
+
item: {
|
|
2718
|
+
dataType: childInputDef?.dataType || "ANY",
|
|
2719
|
+
scope: scopeName,
|
|
2720
|
+
...childInputDef?.tsType && { tsType: childInputDef.tsType }
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
availableNodeTypes.push(syntheticNodeType);
|
|
2725
|
+
const mapInstance = {
|
|
2726
|
+
type: "NodeInstance",
|
|
2727
|
+
id: instanceId,
|
|
2728
|
+
nodeType: syntheticTypeName
|
|
2729
|
+
};
|
|
2730
|
+
instances.push(mapInstance);
|
|
2731
|
+
childInstance.parent = { id: instanceId, scope: scopeName };
|
|
2732
|
+
connections.push({
|
|
2733
|
+
type: "Connection",
|
|
2734
|
+
from: { node: instanceId, port: "start", scope: scopeName },
|
|
2735
|
+
to: { node: childId, port: "execute", scope: scopeName }
|
|
2736
|
+
});
|
|
2737
|
+
connections.push({
|
|
2738
|
+
type: "Connection",
|
|
2739
|
+
from: { node: instanceId, port: "item", scope: scopeName },
|
|
2740
|
+
to: { node: childId, port: inputPort, scope: scopeName }
|
|
2741
|
+
});
|
|
2742
|
+
connections.push({
|
|
2743
|
+
type: "Connection",
|
|
2744
|
+
from: { node: childId, port: outputPort, scope: scopeName },
|
|
2745
|
+
to: { node: instanceId, port: "processed", scope: scopeName }
|
|
2746
|
+
});
|
|
2747
|
+
connections.push({
|
|
2748
|
+
type: "Connection",
|
|
2749
|
+
from: { node: childId, port: "onSuccess", scope: scopeName },
|
|
2750
|
+
to: { node: instanceId, port: "success", scope: scopeName }
|
|
2751
|
+
});
|
|
2752
|
+
connections.push({
|
|
2753
|
+
type: "Connection",
|
|
2754
|
+
from: { node: childId, port: "onFailure", scope: scopeName },
|
|
2755
|
+
to: { node: instanceId, port: "failure", scope: scopeName }
|
|
2756
|
+
});
|
|
2757
|
+
connections.push({
|
|
2758
|
+
type: "Connection",
|
|
2759
|
+
from: { node: sourceNode, port: sourcePort },
|
|
2760
|
+
to: { node: instanceId, port: "items" }
|
|
2761
|
+
});
|
|
2762
|
+
scopes[`${instanceId}.${scopeName}`] = [childId];
|
|
2763
|
+
macros.push({
|
|
2764
|
+
type: "map",
|
|
2765
|
+
instanceId,
|
|
2766
|
+
childId,
|
|
2767
|
+
sourcePort: `${sourceNode}.${sourcePort}`,
|
|
2768
|
+
...mapConfig.inputPort && { inputPort: mapConfig.inputPort },
|
|
2769
|
+
...mapConfig.outputPort && { outputPort: mapConfig.outputPort }
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
function expandPathMacros(pathConfigs, instances, connections, availableNodeTypes, startPorts, exitPorts, macros, errors, _warnings) {
|
|
2773
|
+
const findNodeType = (nodeTypeName) => availableNodeTypes.find(
|
|
2774
|
+
(nt) => nt.name === nodeTypeName || nt.functionName === nodeTypeName
|
|
2775
|
+
);
|
|
2776
|
+
const getNodeType = (nodeId) => {
|
|
2777
|
+
const instance = instances.find((inst) => inst.id === nodeId);
|
|
2778
|
+
if (!instance) return void 0;
|
|
2779
|
+
return findNodeType(instance.nodeType);
|
|
2780
|
+
};
|
|
2781
|
+
const getOutputPorts = (nodeId) => {
|
|
2782
|
+
if (nodeId === "Start") return startPorts;
|
|
2783
|
+
if (nodeId === "Exit") return exitPorts;
|
|
2784
|
+
const nodeType = getNodeType(nodeId);
|
|
2785
|
+
return nodeType?.outputs || {};
|
|
2786
|
+
};
|
|
2787
|
+
const getInputPorts = (nodeId) => {
|
|
2788
|
+
if (nodeId === "Exit") return exitPorts;
|
|
2789
|
+
if (nodeId === "Start") return startPorts;
|
|
2790
|
+
const nodeType = getNodeType(nodeId);
|
|
2791
|
+
return nodeType?.inputs || {};
|
|
2792
|
+
};
|
|
2793
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
2794
|
+
for (const conn of connections) {
|
|
2795
|
+
existingKeys.add(`${conn.from.node}.${conn.from.port}->${conn.to.node}.${conn.to.port}`);
|
|
2796
|
+
}
|
|
2797
|
+
const addConnection = (fromNode, fromPort, toNode, toPort) => {
|
|
2798
|
+
const key = `${fromNode}.${fromPort}->${toNode}.${toPort}`;
|
|
2799
|
+
if (existingKeys.has(key)) return;
|
|
2800
|
+
existingKeys.add(key);
|
|
2801
|
+
connections.push({
|
|
2802
|
+
type: "Connection",
|
|
2803
|
+
from: { node: fromNode, port: fromPort },
|
|
2804
|
+
to: { node: toNode, port: toPort }
|
|
2805
|
+
});
|
|
2806
|
+
};
|
|
2807
|
+
for (const pathConfig of pathConfigs) {
|
|
2808
|
+
const { steps } = pathConfig;
|
|
2809
|
+
if (steps.length < 2) {
|
|
2810
|
+
errors.push(`@path requires at least 2 steps, got ${steps.length}.`);
|
|
2811
|
+
continue;
|
|
2812
|
+
}
|
|
2813
|
+
let valid = true;
|
|
2814
|
+
for (const step of steps) {
|
|
2815
|
+
if (step.node === "Start" || step.node === "Exit") continue;
|
|
2816
|
+
const instance = instances.find((inst) => inst.id === step.node);
|
|
2817
|
+
if (!instance) {
|
|
2818
|
+
errors.push(
|
|
2819
|
+
`@path: node "${step.node}" not found. Declare it with @node before using @path.`
|
|
2820
|
+
);
|
|
2821
|
+
valid = false;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
if (!valid) continue;
|
|
2825
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
2826
|
+
const current = steps[i];
|
|
2827
|
+
const next = steps[i + 1];
|
|
2828
|
+
const currentId = current.node;
|
|
2829
|
+
const nextId = next.node;
|
|
2830
|
+
const route = current.route || "ok";
|
|
2831
|
+
if (currentId === "Start") {
|
|
2832
|
+
addConnection("Start", "execute", nextId, nextId === "Exit" ? "onSuccess" : "execute");
|
|
2833
|
+
} else if (nextId === "Exit") {
|
|
2834
|
+
if (route === "fail") {
|
|
2835
|
+
addConnection(currentId, "onFailure", "Exit", "onFailure");
|
|
2836
|
+
} else {
|
|
2837
|
+
addConnection(currentId, "onSuccess", "Exit", "onSuccess");
|
|
2838
|
+
}
|
|
2839
|
+
} else {
|
|
2840
|
+
if (route === "fail") {
|
|
2841
|
+
addConnection(currentId, "onFailure", nextId, "execute");
|
|
2842
|
+
} else {
|
|
2843
|
+
addConnection(currentId, "onSuccess", nextId, "execute");
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
for (const edge of impliedPathDataEdgesInto(steps, i + 1, { inputs: getInputPorts, outputs: getOutputPorts })) {
|
|
2847
|
+
const alreadyConnected = connections.some(
|
|
2848
|
+
(c) => c.to.node === edge.to.node && c.to.port === edge.to.port
|
|
2849
|
+
);
|
|
2850
|
+
if (alreadyConnected) continue;
|
|
2851
|
+
addConnection(edge.from.node, edge.from.port, edge.to.node, edge.to.port);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
macros.push({
|
|
2855
|
+
type: "path",
|
|
2856
|
+
steps: steps.map((s) => s.route ? { node: s.node, route: s.route } : { node: s.node })
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
function expandFanOutMacros(fanOutConfigs, instances, connections, startPorts, exitPorts, macros, errors) {
|
|
2861
|
+
const instanceIds = new Set(instances.map((i) => i.id));
|
|
2862
|
+
instanceIds.add("Start");
|
|
2863
|
+
instanceIds.add("Exit");
|
|
2864
|
+
for (const config of fanOutConfigs) {
|
|
2865
|
+
const { source, targets } = config;
|
|
2866
|
+
if (!instanceIds.has(source.node)) {
|
|
2867
|
+
errors.push(`@fanOut: source node "${source.node}" does not exist`);
|
|
2868
|
+
continue;
|
|
2869
|
+
}
|
|
2870
|
+
let valid = true;
|
|
2871
|
+
for (const target of targets) {
|
|
2872
|
+
if (!instanceIds.has(target.node)) {
|
|
2873
|
+
errors.push(`@fanOut: target node "${target.node}" does not exist`);
|
|
2874
|
+
valid = false;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
if (!valid) continue;
|
|
2878
|
+
for (const target of targets) {
|
|
2879
|
+
const targetPort = target.port ?? source.port;
|
|
2880
|
+
const conn = {
|
|
2881
|
+
type: "Connection",
|
|
2882
|
+
from: { node: source.node, port: source.port },
|
|
2883
|
+
to: { node: target.node, port: targetPort }
|
|
2884
|
+
};
|
|
2885
|
+
const exists = connections.some(
|
|
2886
|
+
(c) => c.from.node === conn.from.node && c.from.port === conn.from.port && c.to.node === conn.to.node && c.to.port === conn.to.port
|
|
2887
|
+
);
|
|
2888
|
+
if (!exists) {
|
|
2889
|
+
connections.push(conn);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
macros.push({
|
|
2893
|
+
type: "fanOut",
|
|
2894
|
+
source: { node: source.node, port: source.port },
|
|
2895
|
+
targets: targets.map((t) => t.port ? { node: t.node, port: t.port } : { node: t.node })
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
function expandFanInMacros(fanInConfigs, instances, connections, startPorts, exitPorts, macros, errors) {
|
|
2900
|
+
const instanceIds = new Set(instances.map((i) => i.id));
|
|
2901
|
+
instanceIds.add("Start");
|
|
2902
|
+
instanceIds.add("Exit");
|
|
2903
|
+
for (const config of fanInConfigs) {
|
|
2904
|
+
const { sources, target } = config;
|
|
2905
|
+
if (!instanceIds.has(target.node)) {
|
|
2906
|
+
errors.push(`@fanIn: target node "${target.node}" does not exist`);
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
let valid = true;
|
|
2910
|
+
for (const source of sources) {
|
|
2911
|
+
if (!instanceIds.has(source.node)) {
|
|
2912
|
+
errors.push(`@fanIn: source node "${source.node}" does not exist`);
|
|
2913
|
+
valid = false;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
if (!valid) continue;
|
|
2917
|
+
for (const source of sources) {
|
|
2918
|
+
const sourcePort = source.port ?? target.port;
|
|
2919
|
+
const conn = {
|
|
2920
|
+
type: "Connection",
|
|
2921
|
+
from: { node: source.node, port: sourcePort },
|
|
2922
|
+
to: { node: target.node, port: target.port }
|
|
2923
|
+
};
|
|
2924
|
+
const exists = connections.some(
|
|
2925
|
+
(c) => c.from.node === conn.from.node && c.from.port === conn.from.port && c.to.node === conn.to.node && c.to.port === conn.to.port
|
|
2926
|
+
);
|
|
2927
|
+
if (!exists) {
|
|
2928
|
+
connections.push(conn);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
macros.push({
|
|
2932
|
+
type: "fanIn",
|
|
2933
|
+
sources: sources.map((s) => s.port ? { node: s.node, port: s.port } : { node: s.node }),
|
|
2934
|
+
target: { node: target.node, port: target.port }
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function expandCoerceMacros(coerceConfigs, instances, connections, startPorts, exitPorts, macros, errors) {
|
|
2939
|
+
const instanceIds = new Set(instances.map((i) => i.id));
|
|
2940
|
+
instanceIds.add("Start");
|
|
2941
|
+
instanceIds.add("Exit");
|
|
2942
|
+
for (const config of coerceConfigs) {
|
|
2943
|
+
const { instanceId, source, target, targetType } = config;
|
|
2944
|
+
if (!instanceIds.has(source.node)) {
|
|
2945
|
+
errors.push(`@coerce: source node "${source.node}" does not exist`);
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2948
|
+
if (!instanceIds.has(target.node)) {
|
|
2949
|
+
errors.push(`@coerce: target node "${target.node}" does not exist`);
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
if (instanceIds.has(instanceId)) {
|
|
2953
|
+
errors.push(`@coerce: instance ID "${instanceId}" already exists`);
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
const nodeTypeName = COERCE_TYPE_MAP[targetType];
|
|
2957
|
+
if (!nodeTypeName) {
|
|
2958
|
+
errors.push(`@coerce: unknown target type "${targetType}"`);
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
instances.push({
|
|
2962
|
+
type: "NodeInstance",
|
|
2963
|
+
id: instanceId,
|
|
2964
|
+
nodeType: nodeTypeName
|
|
2965
|
+
});
|
|
2966
|
+
instanceIds.add(instanceId);
|
|
2967
|
+
connections.push({
|
|
2968
|
+
type: "Connection",
|
|
2969
|
+
from: { node: source.node, port: source.port },
|
|
2970
|
+
to: { node: instanceId, port: "value" }
|
|
2971
|
+
});
|
|
2972
|
+
connections.push({
|
|
2973
|
+
type: "Connection",
|
|
2974
|
+
from: { node: instanceId, port: "result" },
|
|
2975
|
+
to: { node: target.node, port: target.port }
|
|
2976
|
+
});
|
|
2977
|
+
macros.push({
|
|
2978
|
+
type: "coerce",
|
|
2979
|
+
instanceId,
|
|
2980
|
+
source: { node: source.node, port: source.port },
|
|
2981
|
+
target: { node: target.node, port: target.port },
|
|
2982
|
+
targetType
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
function generateAutoConnections(instances, availableNodeTypes, startPorts, exitPorts) {
|
|
2987
|
+
const connections = [];
|
|
2988
|
+
const findNodeType = (nodeTypeName) => availableNodeTypes.find(
|
|
2989
|
+
(nt) => nt.name === nodeTypeName || nt.functionName === nodeTypeName
|
|
2990
|
+
);
|
|
2991
|
+
if (instances.length > 0) {
|
|
2992
|
+
const firstInstance = instances[0];
|
|
2993
|
+
const firstNodeType = findNodeType(firstInstance.nodeType);
|
|
2994
|
+
connections.push({
|
|
2995
|
+
type: "Connection",
|
|
2996
|
+
from: { node: "Start", port: "execute" },
|
|
2997
|
+
to: { node: firstInstance.id, port: "execute" }
|
|
2998
|
+
});
|
|
2999
|
+
if (firstNodeType) {
|
|
3000
|
+
for (const [portName, portDef] of Object.entries(startPorts)) {
|
|
3001
|
+
if (portDef.dataType === "STEP") continue;
|
|
3002
|
+
if (portName in firstNodeType.inputs && !isControlFlowPort(portName)) {
|
|
3003
|
+
connections.push({
|
|
3004
|
+
type: "Connection",
|
|
3005
|
+
from: { node: "Start", port: portName },
|
|
3006
|
+
to: { node: firstInstance.id, port: portName }
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
for (let i = 0; i < instances.length - 1; i++) {
|
|
3013
|
+
const current = instances[i];
|
|
3014
|
+
const next = instances[i + 1];
|
|
3015
|
+
const currentNodeType = findNodeType(current.nodeType);
|
|
3016
|
+
const nextNodeType = findNodeType(next.nodeType);
|
|
3017
|
+
connections.push({
|
|
3018
|
+
type: "Connection",
|
|
3019
|
+
from: { node: current.id, port: "onSuccess" },
|
|
3020
|
+
to: { node: next.id, port: "execute" }
|
|
3021
|
+
});
|
|
3022
|
+
if (currentNodeType && nextNodeType) {
|
|
3023
|
+
for (const [outputName, outputDef] of Object.entries(currentNodeType.outputs)) {
|
|
3024
|
+
if (outputDef.dataType === "STEP" || isControlFlowPort(outputName)) continue;
|
|
3025
|
+
if (outputName in nextNodeType.inputs && !isControlFlowPort(outputName)) {
|
|
3026
|
+
connections.push({
|
|
3027
|
+
type: "Connection",
|
|
3028
|
+
from: { node: current.id, port: outputName },
|
|
3029
|
+
to: { node: next.id, port: outputName }
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
if (instances.length > 0) {
|
|
3036
|
+
const lastInstance = instances[instances.length - 1];
|
|
3037
|
+
const lastNodeType = findNodeType(lastInstance.nodeType);
|
|
3038
|
+
connections.push({
|
|
3039
|
+
type: "Connection",
|
|
3040
|
+
from: { node: lastInstance.id, port: "onSuccess" },
|
|
3041
|
+
to: { node: "Exit", port: "onSuccess" }
|
|
3042
|
+
});
|
|
3043
|
+
if (lastNodeType) {
|
|
3044
|
+
for (const [portName, portDef] of Object.entries(exitPorts)) {
|
|
3045
|
+
if (portDef.dataType === "STEP" || portDef.isControlFlow) continue;
|
|
3046
|
+
if (lastNodeType.outputs[portName] && !isControlFlowPort(portName)) {
|
|
3047
|
+
connections.push({
|
|
3048
|
+
type: "Connection",
|
|
3049
|
+
from: { node: lastInstance.id, port: portName },
|
|
3050
|
+
to: { node: "Exit", port: portName }
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return connections;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/parser/import-stub.ts
|
|
3060
|
+
function createImportStub(imp) {
|
|
3061
|
+
return {
|
|
3062
|
+
type: "NodeType",
|
|
3063
|
+
name: imp.name,
|
|
3064
|
+
functionName: imp.functionName,
|
|
3065
|
+
importSource: imp.importSource,
|
|
3066
|
+
variant: "FUNCTION",
|
|
3067
|
+
inputs: {},
|
|
3068
|
+
outputs: { result: { dataType: "ANY" } },
|
|
3069
|
+
hasSuccessPort: true,
|
|
3070
|
+
hasFailurePort: true,
|
|
3071
|
+
executeWhen: "CONJUNCTION",
|
|
3072
|
+
isAsync: false,
|
|
3073
|
+
// Mark as expression since most npm functions are pure
|
|
3074
|
+
// This is a reasonable default for stubs
|
|
3075
|
+
expression: true
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
function isImportStub(nt) {
|
|
3079
|
+
if (!nt.importSource) return false;
|
|
3080
|
+
const inputKeys = Object.keys(nt.inputs ?? {});
|
|
3081
|
+
const outputKeys = Object.keys(nt.outputs ?? {});
|
|
3082
|
+
return inputKeys.length === 0 && outputKeys.length === 1 && outputKeys[0] === "result";
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
// src/parser/workflow-extraction.ts
|
|
3086
|
+
var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
3087
|
+
"strictTypes",
|
|
3088
|
+
"autoConnect",
|
|
3089
|
+
"trigger",
|
|
3090
|
+
"http",
|
|
3091
|
+
"cancelOn",
|
|
3092
|
+
"retries",
|
|
3093
|
+
"timeout",
|
|
3094
|
+
"throttle",
|
|
3095
|
+
"deploy"
|
|
3096
|
+
]);
|
|
3097
|
+
function promoteDeployNamespaces(deploy) {
|
|
3098
|
+
if (!deploy) return {};
|
|
3099
|
+
const promoted = {};
|
|
3100
|
+
for (const [namespace, data] of Object.entries(deploy)) {
|
|
3101
|
+
if (RESERVED_OPTION_KEYS.has(namespace)) continue;
|
|
3102
|
+
if (data && typeof data === "object") promoted[namespace] = data;
|
|
3103
|
+
}
|
|
3104
|
+
return promoted;
|
|
3105
|
+
}
|
|
3106
|
+
function extractWorkflowSignatures(sourceFile, filePath, warnings, tagRegistry) {
|
|
3107
|
+
const workflows = [];
|
|
3108
|
+
extractFunctionLikes(sourceFile).forEach((fn) => {
|
|
3109
|
+
const config = jsdocParser.parseWorkflow(fn, warnings, tagRegistry);
|
|
3110
|
+
if (!config) return;
|
|
3111
|
+
const functionName = fn.getName() || "anonymous";
|
|
3112
|
+
const startPorts = parseStartPorts(fn, config);
|
|
3113
|
+
const exitPorts = parseExitPorts(fn, config, warnings);
|
|
3114
|
+
const userSpecifiedAsync = fn.isAsync();
|
|
3115
|
+
workflows.push({
|
|
3116
|
+
type: "Workflow",
|
|
3117
|
+
sourceFile: filePath,
|
|
3118
|
+
name: config.name || functionName,
|
|
3119
|
+
functionName,
|
|
3120
|
+
nodeTypes: [],
|
|
3121
|
+
instances: [],
|
|
3122
|
+
connections: [],
|
|
3123
|
+
startPorts,
|
|
3124
|
+
exitPorts,
|
|
3125
|
+
imports: [],
|
|
3126
|
+
description: config.description,
|
|
3127
|
+
userSpecifiedAsync
|
|
3128
|
+
});
|
|
3129
|
+
});
|
|
3130
|
+
return workflows;
|
|
3131
|
+
}
|
|
3132
|
+
function workflowToNodeType(workflow) {
|
|
3133
|
+
return {
|
|
3134
|
+
type: "NodeType",
|
|
3135
|
+
name: workflow.name,
|
|
3136
|
+
functionName: workflow.functionName,
|
|
3137
|
+
variant: "IMPORTED_WORKFLOW",
|
|
3138
|
+
path: workflow.sourceFile,
|
|
3139
|
+
inputs: { ...workflow.startPorts },
|
|
3140
|
+
outputs: { ...workflow.exitPorts },
|
|
3141
|
+
hasSuccessPort: "onSuccess" in workflow.exitPorts,
|
|
3142
|
+
hasFailurePort: "onFailure" in workflow.exitPorts,
|
|
3143
|
+
isAsync: workflow.userSpecifiedAsync || false,
|
|
3144
|
+
executeWhen: EXECUTION_STRATEGIES.CONJUNCTION,
|
|
3145
|
+
description: workflow.description,
|
|
3146
|
+
sourceLocation: {
|
|
3147
|
+
file: workflow.sourceFile,
|
|
3148
|
+
line: 0,
|
|
3149
|
+
column: 0
|
|
3150
|
+
}
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
function extractWorkflows(sourceFile, availableNodeTypes, filePath, errors, warnings, tagRegistry, resolveImport) {
|
|
3154
|
+
const workflows = [];
|
|
3155
|
+
const allFunctions = extractFunctionLikes(sourceFile);
|
|
3156
|
+
const allFunctionNames = allFunctions.map((fn) => fn.getName()).filter((name) => !!name);
|
|
3157
|
+
allFunctions.forEach((fn) => {
|
|
3158
|
+
const config = jsdocParser.parseWorkflow(fn, warnings, tagRegistry);
|
|
3159
|
+
if (!config) {
|
|
3160
|
+
const jsdocText = fn.getJsDocs().map((d) => d.getFullText()).join("");
|
|
3161
|
+
if (jsdocText.includes("@flowWeaver workflow")) {
|
|
3162
|
+
warnings.push(
|
|
3163
|
+
`Function "${fn.getName() || "anonymous"}" has @flowWeaver annotation but could not be parsed. Check for special characters (---) or malformed JSDoc syntax.`
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
const functionName = fn.getName() || "anonymous";
|
|
3169
|
+
if (config.connections) {
|
|
3170
|
+
for (const conn of config.connections) {
|
|
3171
|
+
if (conn.from.node === "IN" || conn.from.node === "OUT") {
|
|
3172
|
+
errors.push(
|
|
3173
|
+
`Workflow "${functionName}" uses "${conn.from.node}", which is not a node. Use "Start" or "Exit" instead.`
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
if (conn.to.node === "IN" || conn.to.node === "OUT") {
|
|
3177
|
+
errors.push(
|
|
3178
|
+
`Workflow "${functionName}" uses "${conn.to.node}", which is not a node. Use "Start" or "Exit" instead.`
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
const userSpecifiedAsync = fn.isAsync();
|
|
3184
|
+
const startPorts = parseStartPorts(fn, config);
|
|
3185
|
+
const exitPorts = parseExitPorts(fn, config, warnings);
|
|
3186
|
+
const importedNpmNodeTypes = (config.imports || []).map(
|
|
3187
|
+
(imp) => resolveImport(imp, filePath, warnings)
|
|
3188
|
+
);
|
|
3189
|
+
for (const nt of importedNpmNodeTypes) {
|
|
3190
|
+
const dataInputs = Object.keys(nt.inputs).filter((p) => p !== "execute");
|
|
3191
|
+
const nonControlOutputs = Object.keys(nt.outputs).filter(
|
|
3192
|
+
(p) => p !== "onSuccess" && p !== "onFailure"
|
|
3193
|
+
);
|
|
3194
|
+
const isStubOnly = nonControlOutputs.length === 1 && nonControlOutputs[0] === "result" && nt.outputs.result?.dataType === "ANY";
|
|
3195
|
+
if (dataInputs.length === 0 && isStubOnly) {
|
|
3196
|
+
warnings.push(
|
|
3197
|
+
`Could not infer ports for "${nt.functionName}" from "${nt.importSource}". Wrap it in a local function with @flowWeaver nodeType annotations instead.`
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
const allAvailableNodeTypes = [...availableNodeTypes, ...importedNpmNodeTypes];
|
|
3202
|
+
const instances = (config.instances || []).map((inst) => {
|
|
3203
|
+
let parent;
|
|
3204
|
+
if (inst.parentScope) {
|
|
3205
|
+
const dotIndex = inst.parentScope.indexOf(".");
|
|
3206
|
+
if (dotIndex > 0) {
|
|
3207
|
+
parent = {
|
|
3208
|
+
id: inst.parentScope.substring(0, dotIndex),
|
|
3209
|
+
scope: inst.parentScope.substring(dotIndex + 1)
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
const portConfigs = inst.portConfigs;
|
|
3214
|
+
return {
|
|
3215
|
+
type: "NodeInstance",
|
|
3216
|
+
id: inst.id,
|
|
3217
|
+
nodeType: inst.type,
|
|
3218
|
+
...parent && { parent },
|
|
3219
|
+
config: {
|
|
3220
|
+
...inst.label && { label: inst.label },
|
|
3221
|
+
...portConfigs && portConfigs.length > 0 && { portConfigs },
|
|
3222
|
+
...inst.pullExecution && { pullExecution: inst.pullExecution },
|
|
3223
|
+
...inst.minimized && { minimized: inst.minimized },
|
|
3224
|
+
...inst.color && { color: inst.color },
|
|
3225
|
+
...inst.icon && { icon: inst.icon },
|
|
3226
|
+
...inst.tags && inst.tags.length > 0 && { tags: inst.tags },
|
|
3227
|
+
...inst.width && { width: inst.width },
|
|
3228
|
+
...inst.height && { height: inst.height },
|
|
3229
|
+
...inst.suppressWarnings?.length && { suppressWarnings: inst.suppressWarnings }
|
|
3230
|
+
},
|
|
3231
|
+
...inst.sourceLocation && {
|
|
3232
|
+
sourceLocation: { file: filePath, ...inst.sourceLocation }
|
|
3233
|
+
},
|
|
3234
|
+
...inst.attributes && { attributes: inst.attributes }
|
|
3235
|
+
};
|
|
3236
|
+
});
|
|
3237
|
+
const connections = (config.connections || []).map((conn) => ({
|
|
3238
|
+
type: "Connection",
|
|
3239
|
+
from: conn.from,
|
|
3240
|
+
to: conn.to,
|
|
3241
|
+
...conn.sourceLocation && { sourceLocation: { file: filePath, ...conn.sourceLocation } },
|
|
3242
|
+
...conn.coerce && { coerce: conn.coerce }
|
|
3243
|
+
}));
|
|
3244
|
+
if (config.autoConnect && connections.length === 0 && instances.length > 0) {
|
|
3245
|
+
const autoConnections = generateAutoConnections(
|
|
3246
|
+
instances,
|
|
3247
|
+
allAvailableNodeTypes,
|
|
3248
|
+
startPorts,
|
|
3249
|
+
exitPorts
|
|
3250
|
+
);
|
|
3251
|
+
connections.push(...autoConnections);
|
|
3252
|
+
}
|
|
3253
|
+
const scopes = config.scopes || {};
|
|
3254
|
+
const macros = [];
|
|
3255
|
+
if (config.maps && config.maps.length > 0) {
|
|
3256
|
+
for (const mapConfig of config.maps) {
|
|
3257
|
+
expandMapMacro(
|
|
3258
|
+
mapConfig,
|
|
3259
|
+
instances,
|
|
3260
|
+
connections,
|
|
3261
|
+
scopes,
|
|
3262
|
+
allAvailableNodeTypes,
|
|
3263
|
+
macros,
|
|
3264
|
+
errors,
|
|
3265
|
+
warnings
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
if (config.paths && config.paths.length > 0) {
|
|
3270
|
+
expandPathMacros(
|
|
3271
|
+
config.paths,
|
|
3272
|
+
instances,
|
|
3273
|
+
connections,
|
|
3274
|
+
allAvailableNodeTypes,
|
|
3275
|
+
startPorts,
|
|
3276
|
+
exitPorts,
|
|
3277
|
+
macros,
|
|
3278
|
+
errors,
|
|
3279
|
+
warnings
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
if (config.fanOuts && config.fanOuts.length > 0) {
|
|
3283
|
+
expandFanOutMacros(config.fanOuts, instances, connections, startPorts, exitPorts, macros, errors);
|
|
3284
|
+
}
|
|
3285
|
+
if (config.fanIns && config.fanIns.length > 0) {
|
|
3286
|
+
expandFanInMacros(config.fanIns, instances, connections, startPorts, exitPorts, macros, errors);
|
|
3287
|
+
}
|
|
3288
|
+
if (config.coercions && config.coercions.length > 0) {
|
|
3289
|
+
expandCoerceMacros(config.coercions, instances, connections, startPorts, exitPorts, macros, errors);
|
|
3290
|
+
}
|
|
3291
|
+
if (instances.some((inst) => inst.config?.portConfigs?.some((pc) => pc.expression !== void 0))) {
|
|
3292
|
+
expandExpressionReferences({
|
|
3293
|
+
instances,
|
|
3294
|
+
connections,
|
|
3295
|
+
findNodeType: (name) => allAvailableNodeTypes.find((nt) => nt.name === name || nt.functionName === name),
|
|
3296
|
+
startPorts,
|
|
3297
|
+
moduleBindings: collectModuleBindings(
|
|
3298
|
+
sourceFile,
|
|
3299
|
+
new Set(allAvailableNodeTypes.map((nt) => nt.functionName))
|
|
3300
|
+
),
|
|
3301
|
+
errors
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
const realNameToType = /* @__PURE__ */ new Map();
|
|
3305
|
+
for (const nt of allAvailableNodeTypes) {
|
|
3306
|
+
if (!isImportStub(nt) && !importedNpmNodeTypes.includes(nt)) {
|
|
3307
|
+
realNameToType.set(nt.name, nt);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
const resolvedImportedTypes = importedNpmNodeTypes.map((imp) => {
|
|
3311
|
+
if (isImportStub(imp)) {
|
|
3312
|
+
const real = realNameToType.get(imp.name);
|
|
3313
|
+
if (real) {
|
|
3314
|
+
const merged = { ...real };
|
|
3315
|
+
const impSource = imp.importSource;
|
|
3316
|
+
if (impSource) merged.importSource = impSource;
|
|
3317
|
+
return merged;
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
return imp;
|
|
3321
|
+
});
|
|
3322
|
+
const importedNames = new Set(resolvedImportedTypes.map((nt) => nt.name));
|
|
3323
|
+
const dedupedAvailableTypes = allAvailableNodeTypes.filter((nt) => !importedNames.has(nt.name));
|
|
3324
|
+
const workflowNodeTypes = [...dedupedAvailableTypes, ...resolvedImportedTypes];
|
|
3325
|
+
for (const inst of instances) {
|
|
3326
|
+
if (inst.nodeType.startsWith("__fw_") && COERCION_NODE_TYPES[inst.nodeType]) {
|
|
3327
|
+
if (!workflowNodeTypes.some((nt) => nt.functionName === inst.nodeType)) {
|
|
3328
|
+
workflowNodeTypes.push(COERCION_NODE_TYPES[inst.nodeType]);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
workflows.push({
|
|
3333
|
+
type: "Workflow",
|
|
3334
|
+
sourceFile: filePath,
|
|
3335
|
+
name: config.name || functionName,
|
|
3336
|
+
functionName,
|
|
3337
|
+
nodeTypes: workflowNodeTypes,
|
|
3338
|
+
instances,
|
|
3339
|
+
connections,
|
|
3340
|
+
scopes,
|
|
3341
|
+
startPorts,
|
|
3342
|
+
exitPorts,
|
|
3343
|
+
imports: [],
|
|
3344
|
+
description: config.description,
|
|
3345
|
+
userSpecifiedAsync,
|
|
3346
|
+
availableFunctionNames: allFunctionNames,
|
|
3347
|
+
...macros.length > 0 && { macros },
|
|
3348
|
+
...(config.strictTypes !== void 0 || config.autoConnect || config.trigger || config.http || config.cancelOn || config.retries !== void 0 || config.timeout || config.throttle || config.deploy) && {
|
|
3349
|
+
options: {
|
|
3350
|
+
...config.strictTypes !== void 0 && { strictTypes: config.strictTypes },
|
|
3351
|
+
...config.autoConnect && { autoConnect: true },
|
|
3352
|
+
...config.trigger && { trigger: config.trigger },
|
|
3353
|
+
...config.http && config.http.length > 0 && { http: config.http },
|
|
3354
|
+
...config.cancelOn && { cancelOn: config.cancelOn },
|
|
3355
|
+
...config.retries !== void 0 && { retries: config.retries },
|
|
3356
|
+
...config.timeout && { timeout: config.timeout },
|
|
3357
|
+
...config.throttle && { throttle: config.throttle },
|
|
3358
|
+
// Surface each pack deploy namespace as a top-level options.<namespace>
|
|
3359
|
+
// convenience mirror (e.g. options.<pack> from deploy['<pack>']). Packs
|
|
3360
|
+
// type these fields via module augmentation of TWorkflowOptions; core
|
|
3361
|
+
// stays namespace-agnostic and keeps no vendor vocabulary.
|
|
3362
|
+
...promoteDeployNamespaces(config.deploy),
|
|
3363
|
+
// Per-target deployment config
|
|
3364
|
+
...config.deploy && { deploy: config.deploy }
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3368
|
+
});
|
|
3369
|
+
return workflows;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// src/parser/import-resolution.ts
|
|
3373
|
+
function dependenciesUnchanged(deps) {
|
|
3374
|
+
for (const dep of deps) {
|
|
3375
|
+
try {
|
|
3376
|
+
if (fs2.statSync(dep.path).mtimeMs !== dep.mtime) return false;
|
|
3377
|
+
} catch {
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
return true;
|
|
3382
|
+
}
|
|
3383
|
+
function recordDependency(ctx, filePath, mtime) {
|
|
3384
|
+
for (const recorder of ctx.dependencyRecorders) {
|
|
3385
|
+
recorder.set(filePath, mtime);
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
function extractImportedNodeTypes(ctx, sourceFile, currentFilePath, importResolver, sourceLoader, warnings) {
|
|
3389
|
+
const importedNodeTypes = [];
|
|
3390
|
+
const imports = sourceFile.getImportDeclarations();
|
|
3391
|
+
for (const importDecl of imports) {
|
|
3392
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
3393
|
+
if (!moduleSpecifier) {
|
|
3394
|
+
continue;
|
|
3395
|
+
}
|
|
3396
|
+
if (!moduleSpecifier.startsWith(".")) {
|
|
3397
|
+
const packageNodeTypes = resolveNpmPackageTypes(
|
|
3398
|
+
ctx,
|
|
3399
|
+
importDecl,
|
|
3400
|
+
moduleSpecifier,
|
|
3401
|
+
currentFilePath
|
|
3402
|
+
);
|
|
3403
|
+
importedNodeTypes.push(...packageNodeTypes);
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
const currentDir = path3.dirname(currentFilePath);
|
|
3407
|
+
const importedFilePath = importResolver?.(moduleSpecifier, currentFilePath) ?? resolveModulePath(moduleSpecifier, currentDir, warnings);
|
|
3408
|
+
if (!importedFilePath) {
|
|
3409
|
+
const ext = path3.extname(moduleSpecifier);
|
|
3410
|
+
if (ext && !SOURCE_EXTENSIONS.has(ext)) {
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3413
|
+
throw new Error(
|
|
3414
|
+
`Import error: File not found for "${moduleSpecifier}"
|
|
3415
|
+
Imported from: ${currentFilePath}
|
|
3416
|
+
Searched extensions: .ts, .tsx, .js, .jsx`
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
if (ctx.importStack.has(importedFilePath)) {
|
|
3420
|
+
const cycle = Array.from(ctx.importStack).concat(importedFilePath);
|
|
3421
|
+
throw new Error(`Circular dependency detected:
|
|
3422
|
+
${cycle.join("\n -> ")}`);
|
|
3423
|
+
}
|
|
3424
|
+
try {
|
|
3425
|
+
let nodeTypes;
|
|
3426
|
+
const overriddenSource = sourceLoader?.(importedFilePath);
|
|
3427
|
+
const importStats = overriddenSource === void 0 ? fs2.statSync(importedFilePath) : { mtimeMs: 0 };
|
|
3428
|
+
if (overriddenSource === void 0) {
|
|
3429
|
+
recordDependency(ctx, importedFilePath, importStats.mtimeMs);
|
|
3430
|
+
}
|
|
3431
|
+
const cached = overriddenSource === void 0 ? ctx.importCache.get(importedFilePath) : void 0;
|
|
3432
|
+
if (cached && cached.mtime === importStats.mtimeMs && dependenciesUnchanged(cached.deps)) {
|
|
3433
|
+
nodeTypes = cached.nodeTypes;
|
|
3434
|
+
for (const dep of cached.deps) recordDependency(ctx, dep.path, dep.mtime);
|
|
3435
|
+
} else {
|
|
3436
|
+
ctx.importStack.add(importedFilePath);
|
|
3437
|
+
const importDeps = /* @__PURE__ */ new Map();
|
|
3438
|
+
ctx.dependencyRecorders.push(importDeps);
|
|
3439
|
+
try {
|
|
3440
|
+
const importedRaw = overriddenSource ?? fs2.readFileSync(importedFilePath, "utf-8");
|
|
3441
|
+
const importedContent = hasInPlaceMarkers(importedRaw) ? stripGeneratedSections(importedRaw) : importedRaw;
|
|
3442
|
+
const importedFile = ctx.project.createSourceFile(importedFilePath, importedContent, {
|
|
3443
|
+
overwrite: true
|
|
3444
|
+
});
|
|
3445
|
+
const importWarnings = [];
|
|
3446
|
+
const localNodeTypes = extractNodeTypes(importedFile, importWarnings, ctx.tagRegistry);
|
|
3447
|
+
const importedFromFile = extractImportedNodeTypes(
|
|
3448
|
+
ctx,
|
|
3449
|
+
importedFile,
|
|
3450
|
+
importedFilePath,
|
|
3451
|
+
importResolver,
|
|
3452
|
+
sourceLoader,
|
|
3453
|
+
warnings
|
|
3454
|
+
);
|
|
3455
|
+
const workflows = extractWorkflows(
|
|
3456
|
+
importedFile,
|
|
3457
|
+
[...localNodeTypes, ...importedFromFile],
|
|
3458
|
+
importedFilePath,
|
|
3459
|
+
[],
|
|
3460
|
+
importWarnings,
|
|
3461
|
+
ctx.tagRegistry,
|
|
3462
|
+
(imp, filePath, importAnnotationWarnings) => resolveImportAnnotation(ctx, imp, filePath, importAnnotationWarnings)
|
|
3463
|
+
);
|
|
3464
|
+
const workflowAsNodeTypes = workflows.map((wf) => workflowToNodeType(wf));
|
|
3465
|
+
nodeTypes = [...localNodeTypes, ...importedFromFile, ...workflowAsNodeTypes];
|
|
3466
|
+
const inferredFromImport = inferAllUnannotatedFunctions(importedFile, nodeTypes);
|
|
3467
|
+
nodeTypes.push(...inferredFromImport);
|
|
3468
|
+
if (overriddenSource === void 0) {
|
|
3469
|
+
ctx.project.removeSourceFile(importedFile);
|
|
3470
|
+
}
|
|
3471
|
+
ctx.importCache.set(importedFilePath, {
|
|
3472
|
+
mtime: importStats.mtimeMs,
|
|
3473
|
+
nodeTypes,
|
|
3474
|
+
deps: [...importDeps].map(([path4, mtime]) => ({ path: path4, mtime }))
|
|
3475
|
+
});
|
|
3476
|
+
} finally {
|
|
3477
|
+
ctx.importStack.delete(importedFilePath);
|
|
3478
|
+
ctx.dependencyRecorders.pop();
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
const importedNames = /* @__PURE__ */ new Set();
|
|
3482
|
+
importDecl.getNamedImports().forEach((namedImport) => {
|
|
3483
|
+
importedNames.add(namedImport.getName());
|
|
3484
|
+
});
|
|
3485
|
+
nodeTypes.forEach((nodeType) => {
|
|
3486
|
+
if (importedNames.has(nodeType.functionName)) {
|
|
3487
|
+
importedNodeTypes.push({
|
|
3488
|
+
...nodeType,
|
|
3489
|
+
sourceLocation: {
|
|
3490
|
+
file: importedFilePath,
|
|
3491
|
+
line: nodeType.sourceLocation?.line || 0,
|
|
3492
|
+
column: nodeType.sourceLocation?.column || 0
|
|
3493
|
+
}
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
} catch (error) {
|
|
3498
|
+
if (error instanceof Error) {
|
|
3499
|
+
throw new Error(`Failed to process import from ${importedFilePath}:
|
|
3500
|
+
${error.message}`, { cause: error });
|
|
3501
|
+
}
|
|
3502
|
+
throw error;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
return importedNodeTypes;
|
|
3506
|
+
}
|
|
3507
|
+
function resolveNpmPackageTypes(ctx, importDecl, moduleSpecifier, currentFilePath) {
|
|
3508
|
+
const namedImports = importDecl.getNamedImports();
|
|
3509
|
+
if (namedImports.length === 0) return [];
|
|
3510
|
+
const importedNames = /* @__PURE__ */ new Set();
|
|
3511
|
+
namedImports.forEach((ni) => importedNames.add(ni.getName()));
|
|
3512
|
+
const cacheKey = `npm:${moduleSpecifier}`;
|
|
3513
|
+
const npmCached = ctx.importCache.get(cacheKey);
|
|
3514
|
+
if (npmCached) {
|
|
3515
|
+
const currentDir2 = path3.dirname(currentFilePath);
|
|
3516
|
+
const resolvedDts = resolvePackageTypesPath(moduleSpecifier, currentDir2);
|
|
3517
|
+
if (resolvedDts) {
|
|
3518
|
+
try {
|
|
3519
|
+
const dtsStats = fs2.statSync(resolvedDts);
|
|
3520
|
+
if (npmCached.mtime === dtsStats.mtimeMs) {
|
|
3521
|
+
recordDependency(ctx, resolvedDts, dtsStats.mtimeMs);
|
|
3522
|
+
return npmCached.nodeTypes.filter((nt) => importedNames.has(nt.functionName));
|
|
3523
|
+
}
|
|
3524
|
+
} catch {
|
|
3525
|
+
}
|
|
3526
|
+
} else {
|
|
3527
|
+
return npmCached.nodeTypes.filter((nt) => importedNames.has(nt.functionName));
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
const currentDir = path3.dirname(currentFilePath);
|
|
3531
|
+
const dtsPath = resolvePackageTypesPath(moduleSpecifier, currentDir);
|
|
3532
|
+
if (!dtsPath) return [];
|
|
3533
|
+
try {
|
|
3534
|
+
const dtsContent = fs2.readFileSync(dtsPath, "utf-8");
|
|
3535
|
+
recordDependency(ctx, dtsPath, fs2.statSync(dtsPath).mtimeMs);
|
|
3536
|
+
const dtsFile = ctx.project.createSourceFile(
|
|
3537
|
+
`__npm_dts__/${moduleSpecifier}.d.ts`,
|
|
3538
|
+
dtsContent,
|
|
3539
|
+
{ overwrite: true }
|
|
3540
|
+
);
|
|
3541
|
+
const fns = extractFunctionLikes(dtsFile);
|
|
3542
|
+
const allNodeTypes = [];
|
|
3543
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
3544
|
+
for (const fn of fns) {
|
|
3545
|
+
const fnName = fn.getName();
|
|
3546
|
+
if (!fnName) continue;
|
|
3547
|
+
if (seenNames.has(fnName)) continue;
|
|
3548
|
+
seenNames.add(fnName);
|
|
3549
|
+
const nodeType = inferNodeTypeFromFunction(fn, fnName, dtsPath);
|
|
3550
|
+
nodeType.importSource = moduleSpecifier;
|
|
3551
|
+
nodeType.functionText = void 0;
|
|
3552
|
+
allNodeTypes.push(nodeType);
|
|
3553
|
+
}
|
|
3554
|
+
ctx.project.removeSourceFile(dtsFile);
|
|
3555
|
+
const dtsMtime = fs2.statSync(dtsPath).mtimeMs;
|
|
3556
|
+
ctx.importCache.set(cacheKey, { mtime: dtsMtime, nodeTypes: allNodeTypes, deps: [] });
|
|
3557
|
+
return allNodeTypes.filter((nt) => importedNames.has(nt.functionName));
|
|
3558
|
+
} catch {
|
|
3559
|
+
return [];
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
function resolveImportAnnotation(ctx, imp, currentFilePath, warnings) {
|
|
3563
|
+
const currentDir = path3.dirname(currentFilePath);
|
|
3564
|
+
if (imp.importSource.startsWith(".")) {
|
|
3565
|
+
return resolveLocalImportAnnotation(ctx, imp, currentDir, warnings);
|
|
3566
|
+
} else {
|
|
3567
|
+
return resolveNpmImportAnnotation(ctx, imp, currentDir, warnings);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
function resolveLocalImportAnnotation(ctx, imp, currentDir, warnings) {
|
|
3571
|
+
const importedFilePath = resolveModulePath(imp.importSource, currentDir, warnings);
|
|
3572
|
+
if (!importedFilePath) {
|
|
3573
|
+
warnings.push(`@fwImport: Could not resolve "${imp.importSource}" from ${currentDir}`);
|
|
3574
|
+
return createImportStub(imp);
|
|
3575
|
+
}
|
|
3576
|
+
if (ctx.importStack.has(importedFilePath)) {
|
|
3577
|
+
const cycle = Array.from(ctx.importStack).concat(importedFilePath);
|
|
3578
|
+
warnings.push(`@fwImport: Circular dependency detected:
|
|
3579
|
+
${cycle.join("\n -> ")}`);
|
|
3580
|
+
return createImportStub(imp);
|
|
3581
|
+
}
|
|
3582
|
+
ctx.importStack.add(importedFilePath);
|
|
3583
|
+
try {
|
|
3584
|
+
const importedContent = fs2.readFileSync(importedFilePath, "utf-8");
|
|
3585
|
+
recordDependency(ctx, importedFilePath, fs2.statSync(importedFilePath).mtimeMs);
|
|
3586
|
+
const importedFile = ctx.project.createSourceFile(importedFilePath, importedContent, {
|
|
3587
|
+
overwrite: true
|
|
3588
|
+
});
|
|
3589
|
+
const fns = extractFunctionLikes(importedFile);
|
|
3590
|
+
const fn = fns.find((f) => f.getName() === imp.functionName);
|
|
3591
|
+
if (!fn) {
|
|
3592
|
+
ctx.project.removeSourceFile(importedFile);
|
|
3593
|
+
return createImportStub(imp);
|
|
3594
|
+
}
|
|
3595
|
+
const nodeType = inferNodeTypeFromFunction(fn, imp.name, importedFilePath);
|
|
3596
|
+
nodeType.importSource = imp.importSource;
|
|
3597
|
+
nodeType.functionText = void 0;
|
|
3598
|
+
ctx.project.removeSourceFile(importedFile);
|
|
3599
|
+
return nodeType;
|
|
3600
|
+
} catch {
|
|
3601
|
+
return createImportStub(imp);
|
|
3602
|
+
} finally {
|
|
3603
|
+
ctx.importStack.delete(importedFilePath);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function resolveNpmImportAnnotation(ctx, imp, currentDir, warnings) {
|
|
3607
|
+
const cacheKey = `npm:${imp.importSource}`;
|
|
3608
|
+
if (ctx.importCache.has(cacheKey)) {
|
|
3609
|
+
const cached = ctx.importCache.get(cacheKey);
|
|
3610
|
+
const resolvedDts = resolvePackageTypesPath(imp.importSource, currentDir);
|
|
3611
|
+
let cacheValid = false;
|
|
3612
|
+
if (resolvedDts) {
|
|
3613
|
+
try {
|
|
3614
|
+
const dtsStats = fs2.statSync(resolvedDts);
|
|
3615
|
+
cacheValid = cached.mtime === dtsStats.mtimeMs;
|
|
3616
|
+
} catch {
|
|
3617
|
+
}
|
|
3618
|
+
} else {
|
|
3619
|
+
cacheValid = true;
|
|
3620
|
+
}
|
|
3621
|
+
if (cacheValid) {
|
|
3622
|
+
const found = cached.nodeTypes.find((nt) => nt.functionName === imp.functionName);
|
|
3623
|
+
if (found) {
|
|
3624
|
+
return { ...found, name: imp.name, importSource: imp.importSource };
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
const dtsPath = resolvePackageTypesPath(imp.importSource, currentDir);
|
|
3629
|
+
if (!dtsPath) {
|
|
3630
|
+
warnings.push(
|
|
3631
|
+
`@fwImport: Package "${imp.importSource}" has no type declarations (.d.ts). Install @types/${imp.importSource} or add a local wrapper with @flowWeaver nodeType annotations.`
|
|
3632
|
+
);
|
|
3633
|
+
return createImportStub(imp);
|
|
3634
|
+
}
|
|
3635
|
+
try {
|
|
3636
|
+
const allNodeTypes = inferNodeTypesDeep(
|
|
3637
|
+
ctx.project,
|
|
3638
|
+
dtsPath,
|
|
3639
|
+
imp.importSource,
|
|
3640
|
+
/* @__PURE__ */ new Set(),
|
|
3641
|
+
/* @__PURE__ */ new Set(),
|
|
3642
|
+
0
|
|
3643
|
+
);
|
|
3644
|
+
const dtsMtime2 = fs2.statSync(dtsPath).mtimeMs;
|
|
3645
|
+
recordDependency(ctx, dtsPath, dtsMtime2);
|
|
3646
|
+
ctx.importCache.set(cacheKey, { mtime: dtsMtime2, nodeTypes: allNodeTypes, deps: [] });
|
|
3647
|
+
const found = allNodeTypes.find((nt) => nt.functionName === imp.functionName);
|
|
3648
|
+
if (found) {
|
|
3649
|
+
return { ...found, name: imp.name, importSource: imp.importSource };
|
|
3650
|
+
}
|
|
3651
|
+
warnings.push(
|
|
3652
|
+
`@fwImport: Function "${imp.functionName}" not found in type declarations for "${imp.importSource}". Available exports: ${allNodeTypes.map((nt) => nt.functionName).join(", ") || "(none)"}.`
|
|
3653
|
+
);
|
|
3654
|
+
} catch (err) {
|
|
3655
|
+
warnings.push(
|
|
3656
|
+
`@fwImport: Failed to parse type declarations for "${imp.importSource}": ${err.message}. Node "${imp.name}" will use a generic stub.`
|
|
3657
|
+
);
|
|
3658
|
+
}
|
|
3659
|
+
return createImportStub(imp);
|
|
3660
|
+
}
|
|
3661
|
+
function inferNodeTypesDeep(project, dtsPath, importSource, visitedFiles, seenNames, depth) {
|
|
3662
|
+
const MAX_DEPTH = 8;
|
|
3663
|
+
const resolved = path3.resolve(dtsPath);
|
|
3664
|
+
if (visitedFiles.has(resolved) || depth > MAX_DEPTH) return [];
|
|
3665
|
+
visitedFiles.add(resolved);
|
|
3666
|
+
let dtsContent;
|
|
3667
|
+
try {
|
|
3668
|
+
dtsContent = fs2.readFileSync(resolved, "utf-8");
|
|
3669
|
+
} catch (err) {
|
|
3670
|
+
if (depth === 0) throw err;
|
|
3671
|
+
return [];
|
|
3672
|
+
}
|
|
3673
|
+
const sf = project.createSourceFile(
|
|
3674
|
+
`__npm_dts__/${resolved.replace(/[^A-Za-z0-9._-]/g, "_")}.d.ts`,
|
|
3675
|
+
dtsContent,
|
|
3676
|
+
{ overwrite: true }
|
|
3677
|
+
);
|
|
3678
|
+
const out = [];
|
|
3679
|
+
try {
|
|
3680
|
+
for (const fn of extractFunctionLikes(sf)) {
|
|
3681
|
+
const fnName = fn.getName();
|
|
3682
|
+
if (!fnName) continue;
|
|
3683
|
+
if (seenNames.has(fnName)) continue;
|
|
3684
|
+
seenNames.add(fnName);
|
|
3685
|
+
const nodeType = inferNodeTypeFromFunction(fn, fnName, resolved);
|
|
3686
|
+
nodeType.importSource = importSource;
|
|
3687
|
+
nodeType.functionText = void 0;
|
|
3688
|
+
out.push(nodeType);
|
|
3689
|
+
}
|
|
3690
|
+
const baseDir = path3.dirname(resolved);
|
|
3691
|
+
for (const exp of sf.getExportDeclarations()) {
|
|
3692
|
+
const spec = exp.getModuleSpecifierValue();
|
|
3693
|
+
if (!spec || !spec.startsWith(".")) continue;
|
|
3694
|
+
const target = resolveReExportedDts(baseDir, spec);
|
|
3695
|
+
if (!target) continue;
|
|
3696
|
+
out.push(
|
|
3697
|
+
...inferNodeTypesDeep(project, target, importSource, visitedFiles, seenNames, depth + 1)
|
|
3698
|
+
);
|
|
3699
|
+
}
|
|
3700
|
+
} finally {
|
|
3701
|
+
project.removeSourceFile(sf);
|
|
3702
|
+
}
|
|
3703
|
+
return out;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
// src/parser/external-node-types.ts
|
|
3707
|
+
function externalToAST(ext) {
|
|
3708
|
+
const inputs = {};
|
|
3709
|
+
const outputs = {};
|
|
3710
|
+
const isExpression = ext.expression === true;
|
|
3711
|
+
if (ext.ports) {
|
|
3712
|
+
for (const port of ext.ports) {
|
|
3713
|
+
const def = {
|
|
3714
|
+
dataType: port.type || "ANY",
|
|
3715
|
+
...port.defaultLabel && { label: port.defaultLabel }
|
|
3716
|
+
};
|
|
3717
|
+
if (port.direction === "OUTPUT") {
|
|
3718
|
+
outputs[port.name] = def;
|
|
3719
|
+
} else {
|
|
3720
|
+
inputs[port.name] = def;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
if (!inputs.execute) {
|
|
3725
|
+
inputs.execute = { dataType: "STEP", label: "Execute" };
|
|
3726
|
+
}
|
|
3727
|
+
if (!outputs.onSuccess) {
|
|
3728
|
+
outputs.onSuccess = { dataType: "STEP", label: "On Success", isControlFlow: true };
|
|
3729
|
+
}
|
|
3730
|
+
if (!outputs.onFailure) {
|
|
3731
|
+
outputs.onFailure = {
|
|
3732
|
+
dataType: "STEP",
|
|
3733
|
+
label: "On Failure",
|
|
3734
|
+
isControlFlow: true,
|
|
3735
|
+
failure: true
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
return {
|
|
3739
|
+
type: "NodeType",
|
|
3740
|
+
name: ext.name,
|
|
3741
|
+
functionName: ext.functionName || ext.name,
|
|
3742
|
+
inputs,
|
|
3743
|
+
outputs,
|
|
3744
|
+
hasSuccessPort: "onSuccess" in outputs,
|
|
3745
|
+
hasFailurePort: "onFailure" in outputs,
|
|
3746
|
+
// Honor the supplied async flag so codegen emits `await` for an async
|
|
3747
|
+
// foreign node (e.g. pack-core `waitForApproval`). Defaults to sync
|
|
3748
|
+
// when the caller doesn't say, preserving prior behavior.
|
|
3749
|
+
isAsync: ext.isAsync === true || ext.durableEffect === true,
|
|
3750
|
+
executeWhen: EXECUTION_STRATEGIES.CONJUNCTION,
|
|
3751
|
+
variant: "FUNCTION",
|
|
3752
|
+
// Honor the expression flag so codegen calls the node WITHOUT the
|
|
3753
|
+
// leading `execute` arg (e.g. pack-core `resolveMonth(spec)`).
|
|
3754
|
+
...isExpression && { expression: true },
|
|
3755
|
+
...ext.durableGate && { durableGate: ext.durableGate },
|
|
3756
|
+
...ext.durableEffect === true && { durableEffect: true },
|
|
3757
|
+
...ext.durablePure === true && { durablePure: true },
|
|
3758
|
+
...ext.resilience && { resilience: ext.resilience }
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
function mergeExternalNodeTypes(nodeTypes, externalNodeTypes) {
|
|
3762
|
+
for (const ext of externalNodeTypes) {
|
|
3763
|
+
const existingIdx = nodeTypes.findIndex(
|
|
3764
|
+
(nt) => nt.name === ext.name || nt.functionName === ext.name
|
|
3765
|
+
);
|
|
3766
|
+
if (existingIdx === -1) {
|
|
3767
|
+
nodeTypes.push(externalToAST(ext));
|
|
3768
|
+
continue;
|
|
3769
|
+
}
|
|
3770
|
+
if (isImportStub(nodeTypes[existingIdx])) {
|
|
3771
|
+
const replacement = externalToAST(ext);
|
|
3772
|
+
const stubImportSource = nodeTypes[existingIdx].importSource;
|
|
3773
|
+
if (stubImportSource) {
|
|
3774
|
+
replacement.importSource = stubImportSource;
|
|
3775
|
+
}
|
|
3776
|
+
nodeTypes[existingIdx] = replacement;
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
// src/parser/annotation-suggestion.ts
|
|
3782
|
+
function generateAnnotationSuggestion(project, content, cursorLine, virtualPath = "virtual.ts") {
|
|
3783
|
+
const existingFile = project.getSourceFile(virtualPath);
|
|
3784
|
+
if (existingFile) {
|
|
3785
|
+
project.removeSourceFile(existingFile);
|
|
3786
|
+
}
|
|
3787
|
+
const contentLines = content.split(/\r?\n/);
|
|
3788
|
+
let typedJsDocStart = /^\s*\/\*\*\s*$/.test(contentLines[cursorLine] ?? "");
|
|
3789
|
+
for (let i = cursorLine + 1; typedJsDocStart && i < contentLines.length; i++) {
|
|
3790
|
+
const line = contentLines[i].trim();
|
|
3791
|
+
if (!line.startsWith("*")) break;
|
|
3792
|
+
if (line.includes("*/")) typedJsDocStart = false;
|
|
3793
|
+
}
|
|
3794
|
+
const parseContent = typedJsDocStart ? contentLines.map((line, i) => i === cursorLine ? "" : line).join("\n") : content;
|
|
3795
|
+
const sourceFile = project.createSourceFile(virtualPath, parseContent, { overwrite: true });
|
|
3796
|
+
try {
|
|
3797
|
+
const allFunctions = extractFunctionLikes(sourceFile);
|
|
3798
|
+
if (allFunctions.length === 0) return null;
|
|
3799
|
+
let targetFn = null;
|
|
3800
|
+
let bestDistance = Infinity;
|
|
3801
|
+
for (const fn of allFunctions) {
|
|
3802
|
+
const fnLine = fn.getStartLineNumber(false) - 1;
|
|
3803
|
+
const distance = fnLine >= cursorLine ? fnLine - cursorLine : cursorLine - fnLine + 1e3;
|
|
3804
|
+
if (distance < bestDistance) {
|
|
3805
|
+
bestDistance = distance;
|
|
3806
|
+
targetFn = fn;
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
if (!targetFn) return null;
|
|
3810
|
+
const fnName = targetFn.getName() || "anonymous";
|
|
3811
|
+
const fnStartLine = targetFn.getStartLineNumber(false) - 1;
|
|
3812
|
+
if (cursorLine < fnStartLine - 30) return null;
|
|
3813
|
+
const hasAnnotation = hasFlowWeaverAnnotation(targetFn);
|
|
3814
|
+
const hasAnyJsDoc = targetFn.getJsDocs().length > 0;
|
|
3815
|
+
if (hasAnyJsDoc && !hasAnnotation) return null;
|
|
3816
|
+
const inferred = inferNodeTypeFromFunction(targetFn, fnName, virtualPath);
|
|
3817
|
+
const paramDescriptions = /* @__PURE__ */ new Map();
|
|
3818
|
+
for (const doc of targetFn.getJsDocs()) {
|
|
3819
|
+
for (const tag of doc.getTags()) {
|
|
3820
|
+
if (tag.getTagName() === "param") {
|
|
3821
|
+
const comment = tag.getCommentText?.()?.trim() || "";
|
|
3822
|
+
const paramMatch = comment.match(/^(?:\{[^}]*\}\s+)?(\w+)(?:\s*-\s*|\s+)(.+)/);
|
|
3823
|
+
if (paramMatch) {
|
|
3824
|
+
paramDescriptions.set(paramMatch[1], paramMatch[2]);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const [portName, portDef] of Object.entries(inferred.inputs)) {
|
|
3830
|
+
const desc = paramDescriptions.get(portName);
|
|
3831
|
+
if (desc) {
|
|
3832
|
+
portDef.label = desc;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
const existingPorts = extractExistingAnnotatedPorts(targetFn);
|
|
3836
|
+
const missingLines = [];
|
|
3837
|
+
for (const [portName, portDef] of Object.entries(inferred.inputs)) {
|
|
3838
|
+
if (isControlFlowPort(portName)) continue;
|
|
3839
|
+
if (existingPorts.inputs.has(portName)) continue;
|
|
3840
|
+
missingLines.push(` * ${generateJSDocPortTag(portName, portDef, "input")}`);
|
|
3841
|
+
}
|
|
3842
|
+
for (const [portName, portDef] of Object.entries(inferred.outputs)) {
|
|
3843
|
+
if (isControlFlowPort(portName)) continue;
|
|
3844
|
+
if (existingPorts.outputs.has(portName)) continue;
|
|
3845
|
+
missingLines.push(` * ${generateJSDocPortTag(portName, portDef, "output")}`);
|
|
3846
|
+
}
|
|
3847
|
+
if (hasAnnotation) {
|
|
3848
|
+
const isWorkflow = isWorkflowBlock(targetFn);
|
|
3849
|
+
if (isWorkflow) {
|
|
3850
|
+
const connectionLines = generateWorkflowStructureSuggestion(targetFn, sourceFile);
|
|
3851
|
+
missingLines.push(...connectionLines);
|
|
3852
|
+
}
|
|
3853
|
+
if (missingLines.length === 0) return null;
|
|
3854
|
+
const lines2 = content.split(/\r?\n/);
|
|
3855
|
+
let jsDocEndLine = -1;
|
|
3856
|
+
for (let i = fnStartLine - 1; i >= 0; i--) {
|
|
3857
|
+
if (lines2[i].includes("*/")) {
|
|
3858
|
+
jsDocEndLine = i;
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
if (jsDocEndLine < 0) return null;
|
|
3863
|
+
const text2 = missingLines.join("\n") + "\n";
|
|
3864
|
+
return {
|
|
3865
|
+
text: text2,
|
|
3866
|
+
insertLine: jsDocEndLine,
|
|
3867
|
+
replaceLinesCount: 0
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
const lines = content.split(/\r?\n/);
|
|
3871
|
+
const cursorLineText = lines[cursorLine] || "";
|
|
3872
|
+
if (/^\s*\/\*\*\s*$/.test(cursorLineText)) {
|
|
3873
|
+
const continuationLines = [
|
|
3874
|
+
` * @flowWeaver nodeType ${fnName}`,
|
|
3875
|
+
...inferred.expression ? [" * @expression"] : [],
|
|
3876
|
+
...missingLines,
|
|
3877
|
+
" */"
|
|
3878
|
+
];
|
|
3879
|
+
const text2 = continuationLines.join("\n") + "\n";
|
|
3880
|
+
return {
|
|
3881
|
+
text: text2,
|
|
3882
|
+
insertLine: cursorLine + 1,
|
|
3883
|
+
replaceLinesCount: 0
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
const allLines = [
|
|
3887
|
+
"/**",
|
|
3888
|
+
` * @flowWeaver nodeType ${fnName}`,
|
|
3889
|
+
...inferred.expression ? [" * @expression"] : [],
|
|
3890
|
+
...missingLines,
|
|
3891
|
+
" */"
|
|
3892
|
+
];
|
|
3893
|
+
const text = allLines.join("\n") + "\n";
|
|
3894
|
+
return {
|
|
3895
|
+
text,
|
|
3896
|
+
insertLine: fnStartLine,
|
|
3897
|
+
replaceLinesCount: 0
|
|
3898
|
+
};
|
|
3899
|
+
} finally {
|
|
3900
|
+
const sf = project.getSourceFile(virtualPath);
|
|
3901
|
+
if (sf) project.removeSourceFile(sf);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
function isWorkflowBlock(fn) {
|
|
3905
|
+
for (const doc of fn.getJsDocs()) {
|
|
3906
|
+
for (const tag of doc.getTags()) {
|
|
3907
|
+
if (tag.getTagName() !== "flowWeaver") continue;
|
|
3908
|
+
const comment = tag.getCommentText?.()?.trim() || "";
|
|
3909
|
+
const firstWord = comment.split(/\s/)[0];
|
|
3910
|
+
if (firstWord !== "nodeType") {
|
|
3911
|
+
return true;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
return false;
|
|
3916
|
+
}
|
|
3917
|
+
function generateWorkflowStructureSuggestion(fn, sourceFile) {
|
|
3918
|
+
const nodeDecls = /* @__PURE__ */ new Map();
|
|
3919
|
+
const existingConnections = /* @__PURE__ */ new Set();
|
|
3920
|
+
for (const doc of fn.getJsDocs()) {
|
|
3921
|
+
for (const tag of doc.getTags()) {
|
|
3922
|
+
const tagName = tag.getTagName();
|
|
3923
|
+
const comment = tag.getCommentText?.()?.trim() || "";
|
|
3924
|
+
if (tagName === "node") {
|
|
3925
|
+
const nodeMatch = comment.match(/^(\w+)\s+(\w+)/);
|
|
3926
|
+
if (nodeMatch) {
|
|
3927
|
+
nodeDecls.set(nodeMatch[1], nodeMatch[2]);
|
|
3928
|
+
}
|
|
3929
|
+
} else if (tagName === "connect") {
|
|
3930
|
+
const connMatch = comment.match(/^(\w+)\.(\w+)\s*->\s*(\w+)\.(\w+)/);
|
|
3931
|
+
if (connMatch) {
|
|
3932
|
+
existingConnections.add(`${connMatch[1]}.${connMatch[2]}->${connMatch[3]}.${connMatch[4]}`);
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
if (nodeDecls.size < 2) return [];
|
|
3938
|
+
const allFunctions = extractFunctionLikes(sourceFile);
|
|
3939
|
+
const resolvedTypes = /* @__PURE__ */ new Map();
|
|
3940
|
+
for (const [nodeId, typeName] of nodeDecls) {
|
|
3941
|
+
const matchedFn = allFunctions.find((f) => f.getName() === typeName);
|
|
3942
|
+
if (matchedFn) {
|
|
3943
|
+
resolvedTypes.set(nodeId, inferNodeTypeFromFunction(matchedFn, typeName, sourceFile.getFilePath()));
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
const suggestions = [];
|
|
3947
|
+
const nodeIds = [...nodeDecls.keys()];
|
|
3948
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
3949
|
+
for (let j = 0; j < nodeIds.length; j++) {
|
|
3950
|
+
if (i === j) continue;
|
|
3951
|
+
const srcId = nodeIds[i];
|
|
3952
|
+
const tgtId = nodeIds[j];
|
|
3953
|
+
const srcType = resolvedTypes.get(srcId);
|
|
3954
|
+
const tgtType = resolvedTypes.get(tgtId);
|
|
3955
|
+
if (!srcType || !tgtType) continue;
|
|
3956
|
+
for (const [outputName, outputDef] of Object.entries(srcType.outputs)) {
|
|
3957
|
+
if (isControlFlowPort(outputName)) continue;
|
|
3958
|
+
if (outputDef.dataType === "STEP") continue;
|
|
3959
|
+
if (outputName in tgtType.inputs && !isControlFlowPort(outputName)) {
|
|
3960
|
+
const connKey = `${srcId}.${outputName}->${tgtId}.${outputName}`;
|
|
3961
|
+
if (!existingConnections.has(connKey)) {
|
|
3962
|
+
suggestions.push(` * @connect ${srcId}.${outputName} -> ${tgtId}.${outputName}`);
|
|
3963
|
+
existingConnections.add(connKey);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
return suggestions;
|
|
3970
|
+
}
|
|
3971
|
+
function extractExistingAnnotatedPorts(fn) {
|
|
3972
|
+
const inputs = /* @__PURE__ */ new Set();
|
|
3973
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
3974
|
+
for (const doc of fn.getJsDocs()) {
|
|
3975
|
+
for (const tag of doc.getTags()) {
|
|
3976
|
+
const tagName = tag.getTagName();
|
|
3977
|
+
const comment = tag.getCommentText?.()?.trim() || "";
|
|
3978
|
+
const nameMatch = comment.match(/^\[?(\w+)/);
|
|
3979
|
+
if (!nameMatch) continue;
|
|
3980
|
+
const portName = nameMatch[1];
|
|
3981
|
+
if (tagName === "input" || tagName === "step") {
|
|
3982
|
+
inputs.add(portName);
|
|
3983
|
+
} else if (tagName === "output") {
|
|
3984
|
+
outputs.add(portName);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
return { inputs, outputs };
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
// src/parser/pack-handlers.ts
|
|
3992
|
+
async function registerPackHandlers(tagRegistry, projectDir) {
|
|
3993
|
+
const { discoverTagHandlers, discoverValidationRuleSets } = await import("./registry-K2U5KUTY.js");
|
|
3994
|
+
const { pathToFileURL } = await import("node:url");
|
|
3995
|
+
const handlers = await discoverTagHandlers(projectDir);
|
|
3996
|
+
for (const discovered of handlers) {
|
|
3997
|
+
const handlerAlreadyRegistered = discovered.tags.every((t) => tagRegistry.has(t));
|
|
3998
|
+
try {
|
|
3999
|
+
const mod = await import(pathToFileURL(discovered.absoluteFile).href);
|
|
4000
|
+
if (!handlerAlreadyRegistered) {
|
|
4001
|
+
const handlerFn = discovered.exportName ? mod[discovered.exportName] : mod.default;
|
|
4002
|
+
if (typeof handlerFn === "function") {
|
|
4003
|
+
tagRegistry.register(
|
|
4004
|
+
discovered.tags,
|
|
4005
|
+
discovered.namespace,
|
|
4006
|
+
discovered.scope,
|
|
4007
|
+
handlerFn
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
if (discovered.serializerExport) {
|
|
4012
|
+
const serializerFn = mod[discovered.serializerExport];
|
|
4013
|
+
if (typeof serializerFn === "function") {
|
|
4014
|
+
tagRegistry.registerSerializer(discovered.namespace, serializerFn);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
} catch {
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
const { validationRuleRegistry } = await import("./validation-registry-IQZX3UE3.js");
|
|
4021
|
+
const ruleSets = await discoverValidationRuleSets(projectDir);
|
|
4022
|
+
for (const ruleSet of ruleSets) {
|
|
4023
|
+
try {
|
|
4024
|
+
const mod = await import(pathToFileURL(ruleSet.absoluteFile).href);
|
|
4025
|
+
const detectFn = mod[ruleSet.detectExport ?? "detect"];
|
|
4026
|
+
const getRulesFn = mod[ruleSet.rulesExport ?? "getRules"];
|
|
4027
|
+
if (typeof detectFn === "function" && typeof getRulesFn === "function") {
|
|
4028
|
+
validationRuleRegistry.register({
|
|
4029
|
+
name: ruleSet.name,
|
|
4030
|
+
namespace: ruleSet.namespace,
|
|
4031
|
+
detect: detectFn,
|
|
4032
|
+
getRules: getRulesFn
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
} catch {
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// src/parser/annotation-parser.ts
|
|
4041
|
+
var AnnotationParser = class {
|
|
4042
|
+
project;
|
|
4043
|
+
importCache = new LRUCache(200);
|
|
4044
|
+
importStack = /* @__PURE__ */ new Set();
|
|
4045
|
+
parseCache = new LRUCache(100);
|
|
4046
|
+
/**
|
|
4047
|
+
* Dependency recorders for the parses in progress: the outer entry belongs to
|
|
4048
|
+
* the workflow file, and each imported file being processed pushes its own so
|
|
4049
|
+
* its importCache entry can list what it read. A file read while any recorder
|
|
4050
|
+
* is active is added to all of them.
|
|
4051
|
+
*/
|
|
4052
|
+
dependencyRecorders = [];
|
|
4053
|
+
/** Tag handler registry. Defaults to the global singleton (pre-populated by extensions). */
|
|
4054
|
+
tagRegistry = tagHandlerRegistry;
|
|
4055
|
+
/** Tracks which projectDirs have already had their pack handlers loaded. */
|
|
4056
|
+
loadedPackDirs = /* @__PURE__ */ new Set();
|
|
4057
|
+
constructor(project = getSharedProject()) {
|
|
4058
|
+
this.project = project;
|
|
4059
|
+
}
|
|
4060
|
+
/**
|
|
4061
|
+
* Discover and register tag handlers from installed marketplace packs.
|
|
4062
|
+
* Results are cached per projectDir so repeated parse calls skip the scan.
|
|
4063
|
+
*/
|
|
4064
|
+
async loadPackHandlers(projectDir) {
|
|
4065
|
+
if (this.loadedPackDirs.has(projectDir)) return;
|
|
4066
|
+
this.loadedPackDirs.add(projectDir);
|
|
4067
|
+
await registerPackHandlers(this.tagRegistry, projectDir);
|
|
4068
|
+
}
|
|
4069
|
+
computeHash(content) {
|
|
4070
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
4071
|
+
}
|
|
4072
|
+
/** The import state this parser shares with the import resolution functions. */
|
|
4073
|
+
importContext() {
|
|
4074
|
+
return {
|
|
4075
|
+
project: this.project,
|
|
4076
|
+
tagRegistry: this.tagRegistry,
|
|
4077
|
+
importCache: this.importCache,
|
|
4078
|
+
importStack: this.importStack,
|
|
4079
|
+
dependencyRecorders: this.dependencyRecorders
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
parse(filePath, externalNodeTypes) {
|
|
4083
|
+
const stats = fs3.statSync(filePath);
|
|
4084
|
+
const hasExternalTypes = externalNodeTypes && externalNodeTypes.length > 0;
|
|
4085
|
+
if (!hasExternalTypes) {
|
|
4086
|
+
const cached = this.parseCache.get(filePath);
|
|
4087
|
+
const reusable = cached !== void 0 && dependenciesUnchanged(cached.deps);
|
|
4088
|
+
if (cached && reusable && cached.mtime === stats.mtimeMs) {
|
|
4089
|
+
return cached.result;
|
|
4090
|
+
}
|
|
4091
|
+
const rawContent2 = fs3.readFileSync(filePath, "utf-8");
|
|
4092
|
+
const content2 = hasInPlaceMarkers(rawContent2) ? stripGeneratedSections(rawContent2) : rawContent2;
|
|
4093
|
+
const hash2 = this.computeHash(content2);
|
|
4094
|
+
if (cached && reusable && cached.contentHash === hash2) {
|
|
4095
|
+
cached.mtime = stats.mtimeMs;
|
|
4096
|
+
return cached.result;
|
|
4097
|
+
}
|
|
4098
|
+
return this.fullParse(filePath, content2, hash2, stats.mtimeMs);
|
|
4099
|
+
}
|
|
4100
|
+
const rawContent = fs3.readFileSync(filePath, "utf-8");
|
|
4101
|
+
const content = hasInPlaceMarkers(rawContent) ? stripGeneratedSections(rawContent) : rawContent;
|
|
4102
|
+
const hash = this.computeHash(content);
|
|
4103
|
+
return this.fullParse(filePath, content, hash, stats.mtimeMs, externalNodeTypes);
|
|
4104
|
+
}
|
|
4105
|
+
/**
|
|
4106
|
+
* Parse an in-memory source override at its real filesystem path. Relative
|
|
4107
|
+
* imports resolve exactly as they do for parse(), but the override is never
|
|
4108
|
+
* written to disk or stored in the parse cache.
|
|
4109
|
+
*/
|
|
4110
|
+
parseSourceAtPath(filePath, content, externalNodeTypes, importResolver, sourceLoader) {
|
|
4111
|
+
return this.fullParse(
|
|
4112
|
+
filePath,
|
|
4113
|
+
content,
|
|
4114
|
+
this.computeHash(content),
|
|
4115
|
+
0,
|
|
4116
|
+
externalNodeTypes,
|
|
4117
|
+
false,
|
|
4118
|
+
importResolver,
|
|
4119
|
+
sourceLoader
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
fullParse(filePath, content, hash, mtimeMs, externalNodeTypes, cacheResult = true, importResolver, sourceLoader) {
|
|
4123
|
+
this.importStack.clear();
|
|
4124
|
+
const deps = /* @__PURE__ */ new Map();
|
|
4125
|
+
this.dependencyRecorders = [deps];
|
|
4126
|
+
const ctx = this.importContext();
|
|
4127
|
+
const errors = [];
|
|
4128
|
+
const warnings = [];
|
|
4129
|
+
if (sourceLoader !== void 0) {
|
|
4130
|
+
preloadSourceOverrides(
|
|
4131
|
+
this.project,
|
|
4132
|
+
filePath,
|
|
4133
|
+
content,
|
|
4134
|
+
importResolver,
|
|
4135
|
+
sourceLoader,
|
|
4136
|
+
/* @__PURE__ */ new Set([filePath])
|
|
4137
|
+
);
|
|
4138
|
+
}
|
|
4139
|
+
const sourceFile = this.project.createSourceFile(filePath, content, { overwrite: true });
|
|
4140
|
+
this.importStack.add(filePath);
|
|
4141
|
+
const importedNodeTypes = sourceLoader === void 0 ? [] : extractImportedNodeTypes(ctx, sourceFile, filePath, importResolver, sourceLoader, warnings);
|
|
4142
|
+
const localNodeTypes = extractNodeTypes(sourceFile, warnings, this.tagRegistry);
|
|
4143
|
+
if (sourceLoader === void 0) {
|
|
4144
|
+
importedNodeTypes.push(...extractImportedNodeTypes(
|
|
4145
|
+
ctx,
|
|
4146
|
+
sourceFile,
|
|
4147
|
+
filePath,
|
|
4148
|
+
importResolver,
|
|
4149
|
+
sourceLoader,
|
|
4150
|
+
warnings
|
|
4151
|
+
));
|
|
4152
|
+
}
|
|
4153
|
+
const workflowSignatures = extractWorkflowSignatures(sourceFile, filePath, warnings, this.tagRegistry);
|
|
4154
|
+
const sameFileWorkflowNodeTypes = workflowSignatures.map((wf) => workflowToNodeType(wf));
|
|
4155
|
+
const nodeTypes = [...localNodeTypes, ...importedNodeTypes, ...sameFileWorkflowNodeTypes];
|
|
4156
|
+
if (externalNodeTypes?.length) {
|
|
4157
|
+
mergeExternalNodeTypes(nodeTypes, externalNodeTypes);
|
|
4158
|
+
}
|
|
4159
|
+
const inferredNodeTypes = inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
4160
|
+
nodeTypes.push(...inferredNodeTypes);
|
|
4161
|
+
const workflows = extractWorkflows(
|
|
4162
|
+
sourceFile,
|
|
4163
|
+
nodeTypes,
|
|
4164
|
+
filePath,
|
|
4165
|
+
errors,
|
|
4166
|
+
warnings,
|
|
4167
|
+
this.tagRegistry,
|
|
4168
|
+
(imp, currentFilePath, importWarnings) => resolveImportAnnotation(ctx, imp, currentFilePath, importWarnings)
|
|
4169
|
+
);
|
|
4170
|
+
const dedupedWarnings = [...new Set(warnings)];
|
|
4171
|
+
const result = { workflows, nodeTypes, errors, warnings: dedupedWarnings };
|
|
4172
|
+
this.project.removeSourceFile(sourceFile);
|
|
4173
|
+
this.dependencyRecorders = [];
|
|
4174
|
+
if (cacheResult && !externalNodeTypes?.length) {
|
|
4175
|
+
this.parseCache.set(filePath, {
|
|
4176
|
+
mtime: mtimeMs,
|
|
4177
|
+
contentHash: hash,
|
|
4178
|
+
result,
|
|
4179
|
+
deps: [...deps].map(([path4, mtime]) => ({ path: path4, mtime }))
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
return result;
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* Parse workflow from a string instead of a file path.
|
|
4186
|
+
* Useful for testing and in-memory operations.
|
|
4187
|
+
*
|
|
4188
|
+
* Note: Imports from other workflow files are NOT supported in this mode
|
|
4189
|
+
* since there's no filesystem context. Use parse() for files with imports.
|
|
4190
|
+
*
|
|
4191
|
+
* @param code - TypeScript source code containing workflow definitions
|
|
4192
|
+
* @param virtualPath - Virtual file path for error messages (default: 'virtual.ts')
|
|
4193
|
+
* @returns ParseResult with workflows and nodeTypes
|
|
4194
|
+
*/
|
|
4195
|
+
parseFromString(code, virtualPath = "virtual.ts") {
|
|
4196
|
+
this.importStack.clear();
|
|
4197
|
+
const existingFile = this.project.getSourceFile(virtualPath);
|
|
4198
|
+
if (existingFile) {
|
|
4199
|
+
this.project.removeSourceFile(existingFile);
|
|
4200
|
+
}
|
|
4201
|
+
const sourceFile = this.project.createSourceFile(virtualPath, code, { overwrite: true });
|
|
4202
|
+
const errors = [];
|
|
4203
|
+
const warnings = [];
|
|
4204
|
+
const localNodeTypes = extractNodeTypes(sourceFile, warnings, this.tagRegistry);
|
|
4205
|
+
const workflowSignatures = extractWorkflowSignatures(sourceFile, virtualPath, warnings, this.tagRegistry);
|
|
4206
|
+
const sameFileWorkflowNodeTypes = workflowSignatures.map((wf) => workflowToNodeType(wf));
|
|
4207
|
+
const nodeTypes = [...localNodeTypes, ...sameFileWorkflowNodeTypes];
|
|
4208
|
+
const inferredNodeTypes = inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
4209
|
+
nodeTypes.push(...inferredNodeTypes);
|
|
4210
|
+
const ctx = this.importContext();
|
|
4211
|
+
const workflows = extractWorkflows(
|
|
4212
|
+
sourceFile,
|
|
4213
|
+
nodeTypes,
|
|
4214
|
+
virtualPath,
|
|
4215
|
+
errors,
|
|
4216
|
+
warnings,
|
|
4217
|
+
this.tagRegistry,
|
|
4218
|
+
(imp, currentFilePath, importWarnings) => resolveImportAnnotation(ctx, imp, currentFilePath, importWarnings)
|
|
4219
|
+
);
|
|
4220
|
+
this.project.removeSourceFile(sourceFile);
|
|
4221
|
+
const dedupedWarnings = [...new Set(warnings)];
|
|
4222
|
+
return {
|
|
4223
|
+
workflows,
|
|
4224
|
+
nodeTypes,
|
|
4225
|
+
errors,
|
|
4226
|
+
warnings: dedupedWarnings
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
clearCache() {
|
|
4230
|
+
this.importCache.clear();
|
|
4231
|
+
this.parseCache.clear();
|
|
4232
|
+
}
|
|
4233
|
+
/** Clear only the parse result cache, keeping the import/node-type cache intact. */
|
|
4234
|
+
clearParseCache() {
|
|
4235
|
+
this.parseCache.clear();
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* Suggest the `@flowWeaver` JSDoc for the function at or below `cursorLine`
|
|
4239
|
+
* (0-based) in `content`. Returns the text to insert and where, or null when
|
|
4240
|
+
* there is nothing to suggest.
|
|
4241
|
+
*/
|
|
4242
|
+
generateAnnotationSuggestion(content, cursorLine, virtualPath = "virtual.ts") {
|
|
4243
|
+
return generateAnnotationSuggestion(this.project, content, cursorLine, virtualPath);
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
var parser = new AnnotationParser();
|
|
4247
|
+
|
|
4248
|
+
export {
|
|
4249
|
+
MARKERS,
|
|
4250
|
+
hasInPlaceMarkers,
|
|
4251
|
+
stripGeneratedSections,
|
|
4252
|
+
findClosestMatches,
|
|
4253
|
+
resolveNpmNodeTypes,
|
|
4254
|
+
AnnotationParser,
|
|
4255
|
+
parser
|
|
4256
|
+
};
|