@oomol-lab/open-flow 0.1.0-beta.72 → 0.1.0-beta.74

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.
Files changed (432) hide show
  1. package/LICENSE +36 -0
  2. package/NOTICE +7 -0
  3. package/README.md +40 -0
  4. package/dist/browser/{createResourceDialog-BWJkjiOO.js → createResourceDialog-DI9pL-Yr.js} +2 -2
  5. package/dist/browser/{dialog-CUy_fwx9.js → dialog-BYJGvItc.js} +54 -19
  6. package/dist/browser/{flowChanges-CgSnSqqP.js → flowChanges-RL-H4CjD.js} +306 -48
  7. package/dist/browser/{flowWorkspace-Bf7H-r0t.js → flowWorkspace-BkDsETj0.js} +20745 -20186
  8. package/dist/browser/preview.css +73 -3
  9. package/dist/browser/preview.js +21410 -20607
  10. package/dist/browser/{typeScriptSession-B0hC-C8y.js → typeScriptSession-CSRwWmZm.js} +1 -1
  11. package/dist/browser/ui.css +72 -2
  12. package/dist/browser/workbench.css +73 -3
  13. package/dist/browser/workbench.js +14 -76
  14. package/dist/browser/{workbenchSelect-C3e4J93X.js → workbenchSelect-BcWPWGzw.js} +1 -1
  15. package/dist/command/index.js +34 -34
  16. package/dist/common/authoring.d.ts +1 -1
  17. package/dist/common/authoringFields.d.ts +1 -1
  18. package/dist/common/authoringSchema.d.ts +28 -4
  19. package/dist/common/connector-proxy.d.ts +1 -1
  20. package/dist/common/control-api-conformance.d.ts +1 -1
  21. package/dist/common/control-api-conformance.js +61 -24
  22. package/dist/common/control-api.d.ts +4 -3
  23. package/dist/common/control-api.js +90 -42
  24. package/dist/common/control-requests.d.ts +2 -1
  25. package/dist/common/control-requests.js +204 -42
  26. package/dist/common/cron-trigger.d.ts +1 -1
  27. package/dist/common/decision.d.ts +1 -1
  28. package/dist/common/draftOperations.d.ts +1 -1
  29. package/dist/common/engine-contract.d.ts +3 -2
  30. package/dist/common/event-source-definition.d.ts +1 -1
  31. package/dist/common/flow-agent.d.ts +1 -1
  32. package/dist/{browser → common}/flow-authoring-node.d.ts +3 -3
  33. package/dist/{browser → common}/flow-authoring.js +75 -23
  34. package/dist/{browser → common}/flow-change-schema.d.ts +177 -6
  35. package/dist/{browser → common}/flow-change.d.ts +35 -15
  36. package/dist/{browser → common}/flow-change.js +77 -25
  37. package/dist/common/flow-connectionUsage.d.ts +1 -1
  38. package/dist/common/flow-encoding.d.ts +1 -1
  39. package/dist/common/flow-encoding.js +34 -16
  40. package/dist/common/flow-errorHandling.d.ts +1 -1
  41. package/dist/common/flow-graph.d.ts +1 -1
  42. package/dist/common/flow-modules.d.ts +1 -1
  43. package/dist/common/flow-promptTemplate.d.ts +1 -1
  44. package/dist/common/flow-retry.d.ts +1 -1
  45. package/dist/common/flow-semantics.d.ts +1 -1
  46. package/dist/common/flow-semantics.js +151 -32
  47. package/dist/common/flow-workflowFile.d.ts +1 -1
  48. package/dist/common/flowInspection.d.ts +3 -3
  49. package/dist/common/integration-trigger.d.ts +1 -1
  50. package/dist/common/licenses.md +118 -1
  51. package/dist/common/liveDetails.d.ts +1 -1
  52. package/dist/common/mcp.d.ts +2 -1
  53. package/dist/common/mcp.js +59 -22
  54. package/dist/common/openapi.d.ts +1 -1
  55. package/dist/common/poll-trigger.d.ts +1 -1
  56. package/dist/common/run-results.d.ts +8 -1
  57. package/dist/common/runtime-contract.d.ts +8 -2
  58. package/dist/common/runtime-contract.js +22 -5
  59. package/dist/common/scheduler-checkpoint.d.ts +1 -1
  60. package/dist/common/scheduler-dependencies.d.ts +1 -1
  61. package/dist/common/scheduler-node-execution.d.ts +1 -1
  62. package/dist/common/scheduler.d.ts +2 -2
  63. package/dist/common/scheduler.js +31 -12
  64. package/dist/common/trigger-catalog.d.ts +1 -1
  65. package/dist/common/trigger-config-options.d.ts +1 -1
  66. package/dist/common/trigger-config.d.ts +1 -1
  67. package/dist/common/trigger-contract.d.ts +2 -2
  68. package/dist/common/trigger-localization.d.ts +1 -1
  69. package/dist/common/webhook-response.d.ts +1 -1
  70. package/dist/common/webhook-trigger.d.ts +1 -1
  71. package/dist/common/workflow.d.ts +17 -1
  72. package/dist/common/workflow.js +68 -22
  73. package/dist/index.d.ts +6 -3
  74. package/package.json +7 -6
  75. package/src/base/common/json.ts +90 -0
  76. package/src/canvas/common/commands.ts +15 -0
  77. package/src/connector/common/actionSchema.ts +168 -0
  78. package/src/connector/common/proxy.ts +44 -0
  79. package/src/control/common/api.ts +1505 -0
  80. package/src/control/common/authoring.ts +912 -0
  81. package/src/control/common/authoringCondition.ts +110 -0
  82. package/src/control/common/authoringDiagnosticFields.ts +136 -0
  83. package/src/control/common/authoringExamples.ts +192 -0
  84. package/src/control/common/authoringFields.ts +53 -0
  85. package/src/control/common/authoringLlm.ts +70 -0
  86. package/src/control/common/authoringOpenApi.ts +129 -0
  87. package/src/control/common/authoringSchema.ts +404 -0
  88. package/src/control/common/authoringTools.ts +123 -0
  89. package/src/control/common/authoringTriggers.ts +26 -0
  90. package/src/control/common/checkDecoders.ts +65 -0
  91. package/src/control/common/conformance.ts +1989 -0
  92. package/src/control/common/connectorDecoders.ts +370 -0
  93. package/src/control/common/connectorQueries.ts +61 -0
  94. package/src/control/common/decoding.ts +60 -0
  95. package/src/control/common/draftOperations.ts +61 -0
  96. package/src/control/common/errors.ts +114 -0
  97. package/src/control/common/eventSourceSchemas.ts +13 -0
  98. package/src/control/common/eventSources.ts +186 -0
  99. package/src/control/common/flowDecoders.ts +153 -0
  100. package/src/control/common/flowInspection.ts +181 -0
  101. package/src/control/common/flowNotifications.ts +37 -0
  102. package/src/control/common/liveDetails.ts +124 -0
  103. package/src/control/common/mcp.ts +241 -0
  104. package/src/control/common/mcpConformance.ts +142 -0
  105. package/src/control/common/providerAccess.ts +28 -0
  106. package/src/control/common/providerIconSprite.ts +42 -0
  107. package/src/control/common/publicationDecoders.ts +98 -0
  108. package/src/control/common/random.ts +16 -0
  109. package/src/control/common/requests.ts +112 -0
  110. package/src/control/common/resultQuery.ts +8 -0
  111. package/src/control/common/results.ts +176 -0
  112. package/src/control/common/revisionDecoders.ts +52 -0
  113. package/src/control/common/runDecoders.ts +240 -0
  114. package/src/control/common/triggerCatalog.ts +89 -0
  115. package/src/control/common/triggerDecoders.ts +175 -0
  116. package/src/decision/common/decision.ts +168 -0
  117. package/src/event-source/common/catalog.generated.json +65 -0
  118. package/src/event-source/common/definition.ts +66 -0
  119. package/src/event-source/common/index.ts +99 -0
  120. package/src/execution/common/checkpoint.ts +311 -0
  121. package/src/execution/common/dependencies.ts +440 -0
  122. package/src/execution/common/engineContract.ts +80 -0
  123. package/src/execution/common/events.ts +328 -0
  124. package/src/execution/common/node-execution.ts +467 -0
  125. package/src/execution/common/runLifecycle.ts +250 -0
  126. package/src/execution/common/runtime.ts +563 -0
  127. package/src/execution/common/scheduler.ts +696 -0
  128. package/src/execution/common/wait.ts +36 -0
  129. package/src/flow/common/agent.ts +305 -0
  130. package/src/flow/common/authoring.ts +30 -0
  131. package/src/flow/common/change.ts +1036 -0
  132. package/src/flow/common/changeSchema.ts +783 -0
  133. package/src/flow/common/codePermissions.ts +2 -0
  134. package/src/flow/common/condition.ts +265 -0
  135. package/src/flow/common/connectionUsage.ts +131 -0
  136. package/src/flow/common/edgeChanges.ts +35 -0
  137. package/src/flow/common/encoding.ts +437 -0
  138. package/src/flow/common/errorHandling.ts +34 -0
  139. package/src/flow/common/graph.ts +892 -0
  140. package/src/flow/common/inputValue.ts +16 -0
  141. package/src/flow/common/json.ts +8 -0
  142. package/src/flow/common/moduleChanges.ts +31 -0
  143. package/src/flow/common/modules.ts +292 -0
  144. package/src/flow/common/nodeChanges.ts +762 -0
  145. package/src/flow/common/portChanges.ts +36 -0
  146. package/src/flow/common/promptTemplate.ts +15 -0
  147. package/src/flow/common/retry.ts +20 -0
  148. package/src/flow/common/schema.ts +162 -0
  149. package/src/flow/common/schemaCompare.ts +176 -0
  150. package/src/flow/common/schemaComparer.ts +178 -0
  151. package/src/flow/common/schemaKeywords.ts +53 -0
  152. package/src/flow/common/semantics.ts +474 -0
  153. package/src/flow/common/sourceField.ts +35 -0
  154. package/src/flow/common/triggerScheduleSchema.ts +8 -0
  155. package/src/flow/common/wait.ts +30 -0
  156. package/src/flow/common/webhookMethod.ts +7 -0
  157. package/src/flow/common/webhookResponse.ts +75 -0
  158. package/src/flow/common/workflowFile.ts +97 -0
  159. package/src/json-schema-subset/checker/array.ts +142 -0
  160. package/src/json-schema-subset/checker/cache.ts +57 -0
  161. package/src/json-schema-subset/checker/checker.ts +230 -0
  162. package/src/json-schema-subset/checker/enumAndConst.ts +113 -0
  163. package/src/json-schema-subset/checker/equals.ts +93 -0
  164. package/src/json-schema-subset/checker/numeric/checker.ts +50 -0
  165. package/src/json-schema-subset/checker/numeric/float.ts +121 -0
  166. package/src/json-schema-subset/checker/numeric/index.ts +1 -0
  167. package/src/json-schema-subset/checker/numeric/integer.ts +119 -0
  168. package/src/json-schema-subset/checker/numeric/integerNormalizer.ts +72 -0
  169. package/src/json-schema-subset/checker/object.ts +354 -0
  170. package/src/json-schema-subset/checker/range.ts +91 -0
  171. package/src/json-schema-subset/checker/string.ts +60 -0
  172. package/src/json-schema-subset/checker/tools/index.ts +3 -0
  173. package/src/json-schema-subset/checker/tools/mark.ts +82 -0
  174. package/src/json-schema-subset/checker/tools/quantum.ts +106 -0
  175. package/src/json-schema-subset/checker/tools/range.ts +210 -0
  176. package/src/json-schema-subset/checker/validator.ts +23 -0
  177. package/src/json-schema-subset/compiler/array.ts +163 -0
  178. package/src/json-schema-subset/compiler/combination.ts +109 -0
  179. package/src/json-schema-subset/compiler/common.ts +99 -0
  180. package/src/json-schema-subset/compiler/compiler.ts +188 -0
  181. package/src/json-schema-subset/compiler/enumAndConst.ts +75 -0
  182. package/src/json-schema-subset/compiler/index.ts +11 -0
  183. package/src/json-schema-subset/compiler/object.ts +411 -0
  184. package/src/json-schema-subset/compiler/primitive.ts +144 -0
  185. package/src/json-schema-subset/compiler/utils.ts +24 -0
  186. package/src/json-schema-subset/compiler/wrapper.ts +192 -0
  187. package/src/json-schema-subset/error.ts +55 -0
  188. package/src/json-schema-subset/expression/calculator.ts +442 -0
  189. package/src/json-schema-subset/expression/combination.ts +49 -0
  190. package/src/json-schema-subset/expression/expression.ts +191 -0
  191. package/src/json-schema-subset/expression/index.ts +25 -0
  192. package/src/json-schema-subset/expression/result.ts +88 -0
  193. package/src/json-schema-subset/expression/types.ts +36 -0
  194. package/src/json-schema-subset/extends/extendsContext.ts +89 -0
  195. package/src/json-schema-subset/extends/index.ts +2 -0
  196. package/src/json-schema-subset/extends/types.ts +12 -0
  197. package/src/json-schema-subset/global.ts +60 -0
  198. package/src/json-schema-subset/index.ts +8 -0
  199. package/src/json-schema-subset/preprocessing.ts +74 -0
  200. package/src/json-schema-subset/types.ts +8 -0
  201. package/src/json-schema-subset/utils.ts +95 -0
  202. package/src/localization/common/languages.ts +118 -0
  203. package/src/localization/common/timeZone.ts +25 -0
  204. package/src/openapi/common/authBindings.ts +12 -0
  205. package/src/openapi/common/openapi.ts +459 -0
  206. package/src/trigger/common/config.ts +56 -0
  207. package/src/trigger/common/configOptions.ts +15 -0
  208. package/src/trigger/common/configSchema.ts +8 -0
  209. package/src/trigger/common/contract.ts +132 -0
  210. package/src/trigger/common/cron.ts +151 -0
  211. package/src/trigger/common/integration.ts +498 -0
  212. package/src/trigger/common/poll.ts +187 -0
  213. package/src/trigger/common/webhook.ts +412 -0
  214. package/src/trigger/providers/airtable/locales/fr.json +18 -0
  215. package/src/trigger/providers/airtable/locales/ja.json +18 -0
  216. package/src/trigger/providers/airtable/locales/ko.json +18 -0
  217. package/src/trigger/providers/airtable/locales/ru.json +18 -0
  218. package/src/trigger/providers/airtable/locales/zh-CN.json +18 -0
  219. package/src/trigger/providers/airtable/locales/zh-TW.json +18 -0
  220. package/src/trigger/providers/asana/locales/fr.json +15 -0
  221. package/src/trigger/providers/asana/locales/ja.json +15 -0
  222. package/src/trigger/providers/asana/locales/ko.json +15 -0
  223. package/src/trigger/providers/asana/locales/ru.json +15 -0
  224. package/src/trigger/providers/asana/locales/zh-CN.json +15 -0
  225. package/src/trigger/providers/asana/locales/zh-TW.json +15 -0
  226. package/src/trigger/providers/calendly/locales/fr.json +16 -0
  227. package/src/trigger/providers/calendly/locales/ja.json +16 -0
  228. package/src/trigger/providers/calendly/locales/ko.json +16 -0
  229. package/src/trigger/providers/calendly/locales/ru.json +16 -0
  230. package/src/trigger/providers/calendly/locales/zh-CN.json +16 -0
  231. package/src/trigger/providers/calendly/locales/zh-TW.json +16 -0
  232. package/src/trigger/providers/catalog.generated.json +4996 -0
  233. package/src/trigger/providers/clickup/locales/fr.json +16 -0
  234. package/src/trigger/providers/clickup/locales/ja.json +16 -0
  235. package/src/trigger/providers/clickup/locales/ko.json +16 -0
  236. package/src/trigger/providers/clickup/locales/ru.json +16 -0
  237. package/src/trigger/providers/clickup/locales/zh-CN.json +16 -0
  238. package/src/trigger/providers/clickup/locales/zh-TW.json +16 -0
  239. package/src/trigger/providers/definitions.ts +19 -0
  240. package/src/trigger/providers/feishu/locales/fr.json +20 -0
  241. package/src/trigger/providers/feishu/locales/ja.json +20 -0
  242. package/src/trigger/providers/feishu/locales/ko.json +20 -0
  243. package/src/trigger/providers/feishu/locales/ru.json +20 -0
  244. package/src/trigger/providers/feishu/locales/zh-CN.json +20 -0
  245. package/src/trigger/providers/feishu/locales/zh-TW.json +20 -0
  246. package/src/trigger/providers/fieldDescriptions.ts +392 -0
  247. package/src/trigger/providers/fieldLabels.ts +256 -0
  248. package/src/trigger/providers/fireflies/locales/fr.json +17 -0
  249. package/src/trigger/providers/fireflies/locales/ja.json +17 -0
  250. package/src/trigger/providers/fireflies/locales/ko.json +17 -0
  251. package/src/trigger/providers/fireflies/locales/ru.json +17 -0
  252. package/src/trigger/providers/fireflies/locales/zh-CN.json +17 -0
  253. package/src/trigger/providers/fireflies/locales/zh-TW.json +17 -0
  254. package/src/trigger/providers/github/locales/fr.json +30 -0
  255. package/src/trigger/providers/github/locales/ja.json +30 -0
  256. package/src/trigger/providers/github/locales/ko.json +30 -0
  257. package/src/trigger/providers/github/locales/ru.json +30 -0
  258. package/src/trigger/providers/github/locales/zh-CN.json +30 -0
  259. package/src/trigger/providers/github/locales/zh-TW.json +30 -0
  260. package/src/trigger/providers/gitlab/locales/fr.json +18 -0
  261. package/src/trigger/providers/gitlab/locales/ja.json +18 -0
  262. package/src/trigger/providers/gitlab/locales/ko.json +18 -0
  263. package/src/trigger/providers/gitlab/locales/ru.json +18 -0
  264. package/src/trigger/providers/gitlab/locales/zh-CN.json +18 -0
  265. package/src/trigger/providers/gitlab/locales/zh-TW.json +18 -0
  266. package/src/trigger/providers/gmail/locales/fr.json +18 -0
  267. package/src/trigger/providers/gmail/locales/ja.json +18 -0
  268. package/src/trigger/providers/gmail/locales/ko.json +18 -0
  269. package/src/trigger/providers/gmail/locales/ru.json +18 -0
  270. package/src/trigger/providers/gmail/locales/zh-CN.json +18 -0
  271. package/src/trigger/providers/gmail/locales/zh-TW.json +18 -0
  272. package/src/trigger/providers/google-calendar/locales/fr.json +15 -0
  273. package/src/trigger/providers/google-calendar/locales/ja.json +15 -0
  274. package/src/trigger/providers/google-calendar/locales/ko.json +15 -0
  275. package/src/trigger/providers/google-calendar/locales/ru.json +15 -0
  276. package/src/trigger/providers/google-calendar/locales/zh-CN.json +15 -0
  277. package/src/trigger/providers/google-calendar/locales/zh-TW.json +15 -0
  278. package/src/trigger/providers/google-drive/locales/fr.json +48 -0
  279. package/src/trigger/providers/google-drive/locales/ja.json +48 -0
  280. package/src/trigger/providers/google-drive/locales/ko.json +48 -0
  281. package/src/trigger/providers/google-drive/locales/ru.json +48 -0
  282. package/src/trigger/providers/google-drive/locales/zh-CN.json +48 -0
  283. package/src/trigger/providers/google-drive/locales/zh-TW.json +48 -0
  284. package/src/trigger/providers/google-sheets/locales/fr.json +19 -0
  285. package/src/trigger/providers/google-sheets/locales/ja.json +19 -0
  286. package/src/trigger/providers/google-sheets/locales/ko.json +19 -0
  287. package/src/trigger/providers/google-sheets/locales/ru.json +19 -0
  288. package/src/trigger/providers/google-sheets/locales/zh-CN.json +19 -0
  289. package/src/trigger/providers/google-sheets/locales/zh-TW.json +19 -0
  290. package/src/trigger/providers/intercom/locales/fr.json +17 -0
  291. package/src/trigger/providers/intercom/locales/ja.json +17 -0
  292. package/src/trigger/providers/intercom/locales/ko.json +17 -0
  293. package/src/trigger/providers/intercom/locales/ru.json +17 -0
  294. package/src/trigger/providers/intercom/locales/zh-CN.json +17 -0
  295. package/src/trigger/providers/intercom/locales/zh-TW.json +17 -0
  296. package/src/trigger/providers/jira/locales/fr.json +17 -0
  297. package/src/trigger/providers/jira/locales/ja.json +17 -0
  298. package/src/trigger/providers/jira/locales/ko.json +17 -0
  299. package/src/trigger/providers/jira/locales/ru.json +17 -0
  300. package/src/trigger/providers/jira/locales/zh-CN.json +17 -0
  301. package/src/trigger/providers/jira/locales/zh-TW.json +17 -0
  302. package/src/trigger/providers/linear/locales/fr.json +13 -0
  303. package/src/trigger/providers/linear/locales/ja.json +13 -0
  304. package/src/trigger/providers/linear/locales/ko.json +13 -0
  305. package/src/trigger/providers/linear/locales/ru.json +13 -0
  306. package/src/trigger/providers/linear/locales/zh-CN.json +13 -0
  307. package/src/trigger/providers/linear/locales/zh-TW.json +13 -0
  308. package/src/trigger/providers/locales/fr.ts +71 -0
  309. package/src/trigger/providers/locales/ja.ts +71 -0
  310. package/src/trigger/providers/locales/ko.ts +71 -0
  311. package/src/trigger/providers/locales/ru.ts +71 -0
  312. package/src/trigger/providers/locales/zh-CN.ts +71 -0
  313. package/src/trigger/providers/locales/zh-TW.ts +71 -0
  314. package/src/trigger/providers/localization.ts +59 -0
  315. package/src/trigger/providers/notion/locales/fr.json +16 -0
  316. package/src/trigger/providers/notion/locales/ja.json +16 -0
  317. package/src/trigger/providers/notion/locales/ko.json +16 -0
  318. package/src/trigger/providers/notion/locales/ru.json +16 -0
  319. package/src/trigger/providers/notion/locales/zh-CN.json +16 -0
  320. package/src/trigger/providers/notion/locales/zh-TW.json +16 -0
  321. package/src/trigger/providers/one-drive/locales/fr.json +16 -0
  322. package/src/trigger/providers/one-drive/locales/ja.json +16 -0
  323. package/src/trigger/providers/one-drive/locales/ko.json +16 -0
  324. package/src/trigger/providers/one-drive/locales/ru.json +16 -0
  325. package/src/trigger/providers/one-drive/locales/zh-CN.json +16 -0
  326. package/src/trigger/providers/one-drive/locales/zh-TW.json +16 -0
  327. package/src/trigger/providers/outlook/locales/fr.json +18 -0
  328. package/src/trigger/providers/outlook/locales/ja.json +18 -0
  329. package/src/trigger/providers/outlook/locales/ko.json +18 -0
  330. package/src/trigger/providers/outlook/locales/ru.json +18 -0
  331. package/src/trigger/providers/outlook/locales/zh-CN.json +18 -0
  332. package/src/trigger/providers/outlook/locales/zh-TW.json +18 -0
  333. package/src/trigger/providers/pandadoc/locales/fr.json +12 -0
  334. package/src/trigger/providers/pandadoc/locales/ja.json +12 -0
  335. package/src/trigger/providers/pandadoc/locales/ko.json +12 -0
  336. package/src/trigger/providers/pandadoc/locales/ru.json +12 -0
  337. package/src/trigger/providers/pandadoc/locales/zh-CN.json +12 -0
  338. package/src/trigger/providers/pandadoc/locales/zh-TW.json +12 -0
  339. package/src/trigger/providers/pipedrive/locales/fr.json +15 -0
  340. package/src/trigger/providers/pipedrive/locales/ja.json +15 -0
  341. package/src/trigger/providers/pipedrive/locales/ko.json +15 -0
  342. package/src/trigger/providers/pipedrive/locales/ru.json +15 -0
  343. package/src/trigger/providers/pipedrive/locales/zh-CN.json +15 -0
  344. package/src/trigger/providers/pipedrive/locales/zh-TW.json +15 -0
  345. package/src/trigger/providers/remote.ts +163 -0
  346. package/src/trigger/providers/resend/locales/fr.json +16 -0
  347. package/src/trigger/providers/resend/locales/ja.json +16 -0
  348. package/src/trigger/providers/resend/locales/ko.json +16 -0
  349. package/src/trigger/providers/resend/locales/ru.json +16 -0
  350. package/src/trigger/providers/resend/locales/zh-CN.json +16 -0
  351. package/src/trigger/providers/resend/locales/zh-TW.json +16 -0
  352. package/src/trigger/providers/sentry/locales/fr.json +19 -0
  353. package/src/trigger/providers/sentry/locales/ja.json +19 -0
  354. package/src/trigger/providers/sentry/locales/ko.json +19 -0
  355. package/src/trigger/providers/sentry/locales/ru.json +19 -0
  356. package/src/trigger/providers/sentry/locales/zh-CN.json +19 -0
  357. package/src/trigger/providers/sentry/locales/zh-TW.json +19 -0
  358. package/src/trigger/providers/shopify/locales/fr.json +18 -0
  359. package/src/trigger/providers/shopify/locales/ja.json +18 -0
  360. package/src/trigger/providers/shopify/locales/ko.json +18 -0
  361. package/src/trigger/providers/shopify/locales/ru.json +18 -0
  362. package/src/trigger/providers/shopify/locales/zh-CN.json +18 -0
  363. package/src/trigger/providers/shopify/locales/zh-TW.json +18 -0
  364. package/src/trigger/providers/slack/locales/fr.json +18 -0
  365. package/src/trigger/providers/slack/locales/ja.json +18 -0
  366. package/src/trigger/providers/slack/locales/ko.json +18 -0
  367. package/src/trigger/providers/slack/locales/ru.json +18 -0
  368. package/src/trigger/providers/slack/locales/zh-CN.json +18 -0
  369. package/src/trigger/providers/slack/locales/zh-TW.json +18 -0
  370. package/src/trigger/providers/slackbot/locales/fr.json +23 -0
  371. package/src/trigger/providers/slackbot/locales/ja.json +23 -0
  372. package/src/trigger/providers/slackbot/locales/ko.json +23 -0
  373. package/src/trigger/providers/slackbot/locales/ru.json +23 -0
  374. package/src/trigger/providers/slackbot/locales/zh-CN.json +23 -0
  375. package/src/trigger/providers/slackbot/locales/zh-TW.json +23 -0
  376. package/src/trigger/providers/source-events.ts +25 -0
  377. package/src/trigger/providers/stripe/locales/fr.json +17 -0
  378. package/src/trigger/providers/stripe/locales/ja.json +17 -0
  379. package/src/trigger/providers/stripe/locales/ko.json +17 -0
  380. package/src/trigger/providers/stripe/locales/ru.json +17 -0
  381. package/src/trigger/providers/stripe/locales/zh-CN.json +17 -0
  382. package/src/trigger/providers/stripe/locales/zh-TW.json +17 -0
  383. package/src/trigger/providers/tally/locales/fr.json +12 -0
  384. package/src/trigger/providers/tally/locales/ja.json +12 -0
  385. package/src/trigger/providers/tally/locales/ko.json +12 -0
  386. package/src/trigger/providers/tally/locales/ru.json +12 -0
  387. package/src/trigger/providers/tally/locales/zh-CN.json +12 -0
  388. package/src/trigger/providers/tally/locales/zh-TW.json +12 -0
  389. package/src/trigger/providers/telegram/locales/fr.json +17 -0
  390. package/src/trigger/providers/telegram/locales/ja.json +17 -0
  391. package/src/trigger/providers/telegram/locales/ko.json +17 -0
  392. package/src/trigger/providers/telegram/locales/ru.json +17 -0
  393. package/src/trigger/providers/telegram/locales/zh-CN.json +17 -0
  394. package/src/trigger/providers/telegram/locales/zh-TW.json +17 -0
  395. package/src/trigger/providers/typeform/locales/fr.json +12 -0
  396. package/src/trigger/providers/typeform/locales/ja.json +12 -0
  397. package/src/trigger/providers/typeform/locales/ko.json +12 -0
  398. package/src/trigger/providers/typeform/locales/ru.json +12 -0
  399. package/src/trigger/providers/typeform/locales/zh-CN.json +12 -0
  400. package/src/trigger/providers/typeform/locales/zh-TW.json +12 -0
  401. package/src/trigger/providers/vercel/locales/fr.json +18 -0
  402. package/src/trigger/providers/vercel/locales/ja.json +18 -0
  403. package/src/trigger/providers/vercel/locales/ko.json +18 -0
  404. package/src/trigger/providers/vercel/locales/ru.json +18 -0
  405. package/src/trigger/providers/vercel/locales/zh-CN.json +18 -0
  406. package/src/trigger/providers/vercel/locales/zh-TW.json +18 -0
  407. package/src/trigger/providers/woocommerce/locales/fr.json +19 -0
  408. package/src/trigger/providers/woocommerce/locales/ja.json +19 -0
  409. package/src/trigger/providers/woocommerce/locales/ko.json +19 -0
  410. package/src/trigger/providers/woocommerce/locales/ru.json +19 -0
  411. package/src/trigger/providers/woocommerce/locales/zh-CN.json +19 -0
  412. package/src/trigger/providers/woocommerce/locales/zh-TW.json +19 -0
  413. package/src/trigger/providers/zendesk/locales/fr.json +15 -0
  414. package/src/trigger/providers/zendesk/locales/ja.json +15 -0
  415. package/src/trigger/providers/zendesk/locales/ko.json +15 -0
  416. package/src/trigger/providers/zendesk/locales/ru.json +15 -0
  417. package/src/trigger/providers/zendesk/locales/zh-CN.json +15 -0
  418. package/src/trigger/providers/zendesk/locales/zh-TW.json +15 -0
  419. package/src/trigger/providers/zoom/locales/fr.json +17 -0
  420. package/src/trigger/providers/zoom/locales/ja.json +17 -0
  421. package/src/trigger/providers/zoom/locales/ko.json +17 -0
  422. package/src/trigger/providers/zoom/locales/ru.json +17 -0
  423. package/src/trigger/providers/zoom/locales/zh-CN.json +17 -0
  424. package/src/trigger/providers/zoom/locales/zh-TW.json +17 -0
  425. package/src/types/index.ts +112 -0
  426. package/src/workflow/common/workflow.ts +273 -0
  427. package/dist/browser/lib-DrKav3HG.js +0 -11403
  428. /package/dist/{browser → common}/flow-authoring-edge.d.ts +0 -0
  429. /package/dist/{browser → common}/flow-authoring-module.d.ts +0 -0
  430. /package/dist/{browser → common}/flow-authoring.d.ts +0 -0
  431. /package/dist/{browser → common}/flow-input-value.d.ts +0 -0
  432. /package/dist/{browser → common}/webhook-method.d.ts +0 -0
@@ -0,0 +1,892 @@
1
+ import type {
2
+ BindingSource,
3
+ FlowDocument,
4
+ Graph,
5
+ GraphNode,
6
+ InputMapping,
7
+ InputPortDefinition,
8
+ JsonValue,
9
+ NodeSource,
10
+ PortDefinition,
11
+ RevisionContent,
12
+ SchemaMismatch,
13
+ TriggerNode,
14
+ } from './change.ts'
15
+ import type { Diagnostic } from './semantics.ts'
16
+
17
+ import { missingTriggerConfig, resolveTriggerConfig } from '../../trigger/common/config.ts'
18
+ import { triggerOutputDefinitions, triggerOutputPorts, webhookSupportsBody } from '../../trigger/common/contract.ts'
19
+ import { isTriggerNode, portsByHandle, validVariableName } from './change.ts'
20
+ import { conditionInputPorts, nodeInputMappings, otherwiseOutput, unaryOperator, comparisonIssue, valueType } from './condition.ts'
21
+ import { nodeSystemOutputPorts, supportsNodeErrorHandling } from './errorHandling.ts'
22
+ import { supportsNodeRetry, retryConfigurationSchema } from './retry.ts'
23
+ import { schemaObject, matchesSchema, comparePorts, portsAssignable, variableInputCompatible, hasRetiredRef } from './schema.ts'
24
+ import { sourceFields, sourcePort, sourceOutputLabel } from './sourceField.ts'
25
+ import { waitAutoContinueSchema } from './wait.ts'
26
+ import {
27
+ webhookResponseInputs,
28
+ validWebhookStatus,
29
+ webhookHeaderValues,
30
+ webhookHeaderNameIssue,
31
+ webhookHeaderValuePort,
32
+ webhookHeadersPort,
33
+ webhookResponseAllowsBody,
34
+ webhookStatusCodePort,
35
+ workflowOutputsPort,
36
+ } from './webhookResponse.ts'
37
+
38
+ export function isResolutionNode(node: GraphNode | undefined): node is Extract<GraphNode, { readonly kind: 'approval' | 'wait' }> {
39
+ return node?.kind == 'approval' || node?.kind == 'wait'
40
+ }
41
+ function graphDiagnostic(
42
+ code: string,
43
+ message: string,
44
+ path: string,
45
+ values?: Readonly<Record<string, string | number>>,
46
+ mismatch?: SchemaMismatch,
47
+ ): Diagnostic {
48
+ return { code, column: 0, line: 1, message, mismatch, path, ...(values == null ? {} : { values }) }
49
+ }
50
+
51
+ /** Execution reachability is independent of data bindings and selected runtime branches. */
52
+ export function executionReachableNodes(graph: Graph, source: string): ReadonlySet<string> {
53
+ const reachable = new Set([source])
54
+ for (const id of reachable) for (const edge of graph.edges) if (edge.source === id) reachable.add(edge.target)
55
+ return reachable
56
+ }
57
+
58
+ export function webhookResponseDiagnostics(graph: Graph, triggerId: string, path = `/document/graph/nodes/${triggerId}`): readonly Diagnostic[] {
59
+ const trigger = graph.nodes[triggerId]
60
+ if (trigger?.kind != 'webhook') return []
61
+ const options = trigger.options ?? {}
62
+ const response = options.responseBody
63
+ const statusOutput = options.responseStatusCodeOutput
64
+ const statusPath = `${path}/options/responseStatusCodeOutput`
65
+ const diagnostics: Diagnostic[] = []
66
+ const headersSource = options.responseHeadersSource
67
+ const headerOutputs = options.responseHeaderOutputs
68
+ const headersPath = `${path}/options/${headersSource == null ? 'responseHeaderOutputs' : 'responseHeadersSource'}`
69
+ if (response?.kind !== 'workflow-outputs') {
70
+ if (statusOutput != null)
71
+ diagnostics.push(
72
+ graphDiagnostic('trigger.webhook-response-status-source', 'Choose workflow outputs as the response source to use a status output.', statusPath),
73
+ )
74
+ if (headersSource != null || headerOutputs != null)
75
+ diagnostics.push(
76
+ graphDiagnostic('trigger.webhook-response-headers-source', 'Choose workflow outputs as the response source to use dynamic headers.', headersPath),
77
+ )
78
+ if (response?.kind === 'webhook-response') {
79
+ const location = `${path}/options/responseBody/nodeId`
80
+ const { nodeId } = response
81
+ if (nodeId == null || graph.nodes[nodeId]?.kind !== 'webhook-response')
82
+ diagnostics.push(graphDiagnostic('trigger.webhook-response-node-missing', 'Select a Webhook Response node.', location))
83
+ else if (!executionReachableNodes(graph, triggerId).has(nodeId))
84
+ diagnostics.push(graphDiagnostic('trigger.webhook-response-node-unreachable', 'Connect this Webhook to the selected Webhook Response node.', location))
85
+ }
86
+ return diagnostics
87
+ }
88
+ if (headersSource == null) {
89
+ for (const [headers, location] of [
90
+ [options.responseHeaders, `${path}/options/responseHeaders`],
91
+ [headerOutputs, headersPath],
92
+ ] as const) {
93
+ const names: string[] = []
94
+ for (const name of Object.keys(headers ?? {})) {
95
+ if (webhookHeaderNameIssue(name, names) != null)
96
+ diagnostics.push(graphDiagnostic('trigger.webhook-response-header-name', 'Use valid, unique HTTP header names.', location))
97
+ names.push(name)
98
+ }
99
+ }
100
+ }
101
+ const responsePath = `${path}/options/responseBody`
102
+ const outputs = Object.entries(graph.nodes).filter(([, node]) => node.kind == 'outputs')
103
+ if (outputs.length == 0) {
104
+ diagnostics.push(graphDiagnostic('trigger.webhook-response-outputs-missing', 'Add a workflow Outputs node for the HTTP response.', responsePath))
105
+ } else if (outputs.length == 1) {
106
+ const output = outputs[0]!
107
+ const reachable = executionReachableNodes(graph, triggerId)
108
+ if (!reachable.has(output[0]))
109
+ diagnostics.push(graphDiagnostic('trigger.webhook-response-unreachable', 'Connect this Webhook to the workflow Outputs node.', responsePath))
110
+ const field = response.output
111
+ if (field != null && output[1].kind == 'outputs' && !output[1].outputDefs.some((port) => port.handle == field))
112
+ diagnostics.push(
113
+ graphDiagnostic('trigger.webhook-response-output-missing', `Workflow output "${field}" does not exist.`, `${responsePath}/output`, { output: field }),
114
+ )
115
+ if (statusOutput != null && output[1].kind == 'outputs') {
116
+ const port = output[1].outputDefs.find((candidate) => candidate.handle == statusOutput)
117
+ if (port == null)
118
+ diagnostics.push(
119
+ graphDiagnostic('trigger.webhook-response-status-output-missing', `Workflow status output "${statusOutput}" does not exist.`, statusPath, {
120
+ output: statusOutput,
121
+ }),
122
+ )
123
+ else if (!portsAssignable(port, webhookStatusCodePort))
124
+ diagnostics.push(
125
+ graphDiagnostic('trigger.webhook-response-status-output-type', 'Choose a numeric workflow output for the response status code.', statusPath),
126
+ )
127
+ }
128
+ if (output[1].kind == 'outputs') {
129
+ const fields = output[1].outputDefs
130
+ const checkHeaderSource = (headerField: string | undefined, target: PortDefinition, location: string) => {
131
+ const port = headerField == null ? workflowOutputsPort(fields) : fields.find((candidate) => candidate.handle == headerField)
132
+ if (port == null)
133
+ diagnostics.push(
134
+ graphDiagnostic('trigger.webhook-response-header-output-missing', `Workflow output "${headerField}" does not exist.`, location, {
135
+ output: headerField!,
136
+ }),
137
+ )
138
+ else if (!portsAssignable(port, target))
139
+ diagnostics.push(
140
+ graphDiagnostic('trigger.webhook-response-header-output-type', 'Choose a compatible workflow output for the response headers.', location),
141
+ )
142
+ }
143
+ if (headersSource != null) checkHeaderSource(headersSource.output, webhookHeadersPort, headersPath)
144
+ else
145
+ for (const [name, headerField] of Object.entries(headerOutputs ?? {}))
146
+ checkHeaderSource(headerField, webhookHeaderValuePort, `${headersPath}/${name.replaceAll('~', '~0').replaceAll('/', '~1')}`)
147
+ }
148
+ }
149
+ if (statusOutput == null && !webhookResponseAllowsBody(options.responseStatusCode ?? 200))
150
+ diagnostics.push(
151
+ graphDiagnostic('trigger.webhook-response-status-invalid', 'Choose a response status code that permits a body.', `${path}/options/responseStatusCode`),
152
+ )
153
+ return diagnostics
154
+ }
155
+
156
+ function validateTrigger(triggerId: string, trigger: TriggerNode, path: string, diagnostics: Diagnostic[]): void {
157
+ if (trigger.kind == 'start') return
158
+ const outputs = triggerOutputDefinitions(trigger)
159
+ if (outputs.some((port) => hasRetiredRef(port.jsonSchema)))
160
+ diagnostics.push(graphDiagnostic('graph.schema-unsupported', 'Runtime Ref schemas are not supported.', path))
161
+ const outputHandles = outputs.map((port) => port.handle)
162
+ if (new Set(outputHandles).size !== outputHandles.length)
163
+ diagnostics.push(graphDiagnostic('graph.port-duplicate', 'Trigger output handles must be unique.', `${path}/definition/outputs`))
164
+ if (trigger.kind == 'webhook') {
165
+ if (!webhookSupportsBody(trigger.method) && trigger.bodyFields.length > 0) {
166
+ diagnostics.push(graphDiagnostic('trigger.webhook-body-unsupported', 'GET Webhook triggers cannot declare request body fields.', `${path}/bodyFields`))
167
+ }
168
+ const handles = new Set<string>()
169
+ for (const [index, input] of trigger.bodyFields.entries()) {
170
+ if (handles.has(input.handle)) {
171
+ diagnostics.push(
172
+ graphDiagnostic(
173
+ 'trigger.input-duplicate',
174
+ `Webhook Trigger input "${input.handle}" is declared more than once.`,
175
+ `${path}/bodyFields/${index}/handle`,
176
+ { input: input.handle },
177
+ ),
178
+ )
179
+ }
180
+ handles.add(input.handle)
181
+ }
182
+ return
183
+ }
184
+ if (trigger.kind == 'error' || trigger.kind == 'cron' || trigger.kind == 'manual') return
185
+ if (trigger.connectionId == null)
186
+ diagnostics.push(graphDiagnostic('trigger.connection-missing', 'Select a connection account for this Trigger.', `${path}/connectionId`))
187
+ const missingConfig = missingTriggerConfig(trigger.definition.configInputs, trigger.config)
188
+ if (missingConfig.length > 0) {
189
+ diagnostics.push({
190
+ ...graphDiagnostic('trigger.config-incomplete', `Complete the required Trigger config fields: ${missingConfig.join(', ')}.`, `${path}/config`, {
191
+ fields: missingConfig.join(', '),
192
+ }),
193
+ fields: missingConfig,
194
+ })
195
+ } else {
196
+ try {
197
+ resolveTriggerConfig(trigger.definition.configInputs, trigger.config)
198
+ } catch {
199
+ diagnostics.push(
200
+ graphDiagnostic('trigger.config-invalid', `Trigger "${triggerId}" config does not match its fixed definition.`, `${path}/config`, { triggerId }),
201
+ )
202
+ }
203
+ }
204
+ }
205
+
206
+ export function nodeInputPorts(node: GraphNode): Readonly<Record<string, InputPortDefinition>> {
207
+ switch (node.kind) {
208
+ case 'webhook-response':
209
+ return portsByHandle(webhookResponseInputs)
210
+ case 'outputs':
211
+ return portsByHandle(node.outputDefs)
212
+ case 'condition':
213
+ return conditionInputPorts(node)
214
+ case 'value':
215
+ return {}
216
+ case 'task':
217
+ return portsByHandle([...node.task.inputs, ...(node.additionalInputs ?? [])])
218
+ case 'approval':
219
+ case 'wait':
220
+ return portsByHandle(node.inputDefinitions)
221
+ case 'cron':
222
+ case 'integration':
223
+ case 'poll':
224
+ case 'error':
225
+ case 'manual':
226
+ case 'start':
227
+ case 'webhook':
228
+ return {}
229
+ }
230
+ }
231
+
232
+ export function resolutionActions(node: Extract<GraphNode, { readonly kind: 'approval' | 'wait' }>): readonly ['continue'] | readonly ['approve', 'reject'] {
233
+ return node.kind == 'wait' ? ['continue'] : ['approve', 'reject']
234
+ }
235
+
236
+ const objectPort = (properties: Record<string, JsonValue>): PortDefinition => ({
237
+ nullable: false,
238
+ jsonSchema: { type: 'object', properties, required: Object.keys(properties), additionalProperties: false },
239
+ })
240
+
241
+ export function resolutionOutputPorts(node: Extract<GraphNode, { readonly kind: 'approval' | 'wait' }>): Readonly<Record<string, PortDefinition>> {
242
+ const actions = resolutionActions(node)
243
+ const inputs = {
244
+ type: 'object',
245
+ properties: Object.fromEntries(node.inputDefinitions.map((port) => [port.handle, {}])),
246
+ required: node.inputDefinitions.map((port) => port.handle),
247
+ additionalProperties: false,
248
+ }
249
+
250
+ return {
251
+ pending: objectPort({
252
+ inputs,
253
+ prompt: { type: 'string' },
254
+ pageUrl: { type: 'string' },
255
+ message: { type: 'string', minLength: 1, pattern: '\\S' },
256
+ ...Object.fromEntries(actions.map((action) => [`${action}Url`, { type: 'string' }])),
257
+ expiresAt: { type: 'string' },
258
+ ...(node.kind == 'wait' && node.autoContinue != null ? { autoContinueAt: { type: 'string' } } : {}),
259
+ }),
260
+ ...Object.fromEntries(
261
+ actions.map((action) => [
262
+ action,
263
+ objectPort({
264
+ inputs,
265
+ action: { const: action },
266
+ resolvedAt: { type: 'string' },
267
+ comment: { type: ['string', 'null'] },
268
+ }),
269
+ ]),
270
+ ),
271
+ }
272
+ }
273
+
274
+ export function nodeOutputPorts(node: GraphNode): Readonly<Record<string, PortDefinition>> {
275
+ switch (node.kind) {
276
+ case 'webhook-response':
277
+ case 'outputs':
278
+ case 'condition':
279
+ return {}
280
+ case 'value':
281
+ return portsByHandle(node.values)
282
+ case 'task':
283
+ return portsByHandle(node.task.outputs)
284
+ case 'approval':
285
+ case 'wait':
286
+ return resolutionOutputPorts(node)
287
+ case 'cron':
288
+ case 'integration':
289
+ case 'poll':
290
+ case 'error':
291
+ case 'manual':
292
+ case 'start':
293
+ case 'webhook':
294
+ return triggerOutputPorts(node)
295
+ }
296
+ }
297
+
298
+ function nodeSourceOutputPorts(node: GraphNode, system?: true): Readonly<Record<string, PortDefinition>> {
299
+ if (system) return nodeSystemOutputPorts(node)
300
+ const ports = nodeOutputPorts(node)
301
+ return node.onError == 'error' ? Object.fromEntries(Object.entries(ports).map(([key, port]) => [key, { ...port, nullable: true }])) : ports
302
+ }
303
+
304
+ export function nodeOutputDescription(graph: Graph, nodeId: string, output: string): string | undefined {
305
+ const node = graph.nodes[nodeId]
306
+ return node == null ? undefined : nodeOutputPorts(node)[output]?.description
307
+ }
308
+
309
+ function checkSource(
310
+ source: BindingSource | NodeSource,
311
+ graph: Graph,
312
+ document: FlowDocument,
313
+ targetInput: InputPortDefinition | undefined,
314
+ path: string,
315
+ diagnostics: Diagnostic[],
316
+ ): PortDefinition | undefined {
317
+ switch (source.kind) {
318
+ case 'binding':
319
+ if (document.bindings[source.bindingId] == null) {
320
+ diagnostics.push(graphDiagnostic('graph.binding-missing', `Binding "${source.bindingId}" does not exist.`, path, { bindingId: source.bindingId }))
321
+ } else if (document.bindings[source.bindingId].kind != 'variable') {
322
+ diagnostics.push(graphDiagnostic('graph.binding-invalid', `Binding "${source.bindingId}" must be a variable.`, path, { bindingId: source.bindingId }))
323
+ } else if (targetInput != null && !variableInputCompatible(targetInput.jsonSchema)) {
324
+ diagnostics.push(
325
+ graphDiagnostic('graph.variable-incompatible', `Variable binding "${source.bindingId}" is not compatible with this input.`, path, {
326
+ bindingId: source.bindingId,
327
+ }),
328
+ )
329
+ }
330
+ return
331
+ case 'node': {
332
+ const upstream = graph.nodes[source.nodeId]
333
+ if (upstream == null) {
334
+ diagnostics.push(
335
+ graphDiagnostic('graph.source-missing', `Upstream node "${source.nodeId}" does not exist.`, path, {
336
+ nodeId: source.nodeId,
337
+ variant: 'node',
338
+ }),
339
+ )
340
+ return
341
+ }
342
+ const output = nodeSourceOutputPorts(upstream, source.system)[source.output]
343
+ if (output == null) {
344
+ diagnostics.push(
345
+ graphDiagnostic('graph.source-missing', `Upstream node "${source.nodeId}" does not expose output "${source.output}".`, path, {
346
+ nodeId: source.nodeId,
347
+ output: source.output,
348
+ variant: 'output',
349
+ }),
350
+ )
351
+ }
352
+ if (output == null) return
353
+ const selected = sourcePort(output, source.field)
354
+ if (selected == null) {
355
+ diagnostics.push(
356
+ graphDiagnostic('graph.source-missing', `Output "${source.output}" does not declare field ${JSON.stringify(source.field)}.`, path, {
357
+ nodeId: source.nodeId,
358
+ output: source.output,
359
+ field: source.field!,
360
+ variant: 'field',
361
+ }),
362
+ )
363
+ return
364
+ }
365
+ if (targetInput != null) {
366
+ const comparison = comparePorts(selected, targetInput)
367
+ if (comparison.kind != 'compatible') {
368
+ diagnostics.push(
369
+ graphDiagnostic(
370
+ 'graph.node-output-incompatible',
371
+ `Upstream node "${source.nodeId}" output ${JSON.stringify(sourceOutputLabel(source))} is not compatible with this input.`,
372
+ path,
373
+ { nodeId: source.nodeId, output: source.output, ...(source.field === undefined ? {} : { field: source.field }) },
374
+ comparison.kind == 'incompatible' ? comparison.mismatch : undefined,
375
+ ),
376
+ )
377
+ }
378
+ }
379
+ return selected
380
+ }
381
+ }
382
+ }
383
+
384
+ export function graphOrder(graph: Graph): readonly string[] {
385
+ const pending = new Map(Object.keys(graph.nodes).map((id) => [id, new Set<string>()]))
386
+ const children = new Map<string, Set<string>>()
387
+ for (const edge of graph.edges) {
388
+ if (!pending.has(edge.source) || !pending.has(edge.target)) continue
389
+ pending.get(edge.target)?.add(edge.source)
390
+ const targets = children.get(edge.source) ?? new Set<string>()
391
+ targets.add(edge.target)
392
+ children.set(edge.source, targets)
393
+ }
394
+ const ready = [...pending]
395
+ .filter(([, parents]) => parents.size == 0)
396
+ .map(([id]) => id)
397
+ .toSorted()
398
+ for (let index = 0; index < ready.length; index++) {
399
+ const id = ready[index]!
400
+ pending.delete(id)
401
+ for (const child of children.get(id) ?? []) {
402
+ const parents = pending.get(child)
403
+ parents?.delete(id)
404
+ if (parents?.size == 0) ready.push(child)
405
+ }
406
+ }
407
+ return [...ready, ...[...pending.keys()].toSorted()]
408
+ }
409
+
410
+ const pathsByGraph = new WeakMap<
411
+ Graph,
412
+ {
413
+ readonly paths: Map<string, readonly Route[]>
414
+ readonly ancestors: Map<string, Set<string>>
415
+ readonly resolutionOutputs: Map<string, Set<string>>
416
+ }
417
+ >()
418
+
419
+ // Each route fixes branch choices by node ID; $trigger identifies the selected trigger.
420
+ // An absent key leaves that choice unrestricted, so {} covers every execution path.
421
+ type Route = Readonly<Record<string, string>>
422
+
423
+ function routeCovers(route: Route, target: Route): boolean {
424
+ return Object.entries(route).every(([key, value]) => target[key] == value)
425
+ }
426
+
427
+ function routesCompatible(left: Route, right: Route): boolean {
428
+ return Object.entries(left).every(([key, value]) => right[key] == null || right[key] == value)
429
+ }
430
+
431
+ function resolutionOutputKey(nodeId: string, output: string): string {
432
+ return JSON.stringify([nodeId, output])
433
+ }
434
+
435
+ function graphPaths(graph: Graph) {
436
+ const cached = pathsByGraph.get(graph)
437
+ if (cached != null) return cached
438
+ const paths = new Map<string, readonly Route[]>()
439
+ const ancestors = new Map<string, Set<string>>()
440
+ const resolutionOutputs = new Map<string, Set<string>>()
441
+ const incoming = new Map<string, Graph['edges'][number][]>()
442
+ for (const edge of graph.edges) {
443
+ const edges = incoming.get(edge.target) ?? []
444
+ edges.push(edge)
445
+ incoming.set(edge.target, edges)
446
+ }
447
+ const order = graphOrder(graph)
448
+ let changed = true
449
+ while (changed) {
450
+ changed = false
451
+ for (const id of order) {
452
+ const node = graph.nodes[id]!
453
+ const edges = incoming.get(id) ?? []
454
+ const parents = new Set<string>()
455
+ const resolutions = new Set<string>()
456
+ const routes: Route[] = []
457
+ for (const edge of edges) {
458
+ parents.add(edge.source)
459
+ for (const output of resolutionOutputs.get(edge.source) ?? []) resolutions.add(output)
460
+ if (isResolutionNode(graph.nodes[edge.source]) && edge.sourceHandle != null) resolutions.add(resolutionOutputKey(edge.source, edge.sourceHandle))
461
+ for (const parent of ancestors.get(edge.source) ?? []) parents.add(parent)
462
+ for (const route of paths.get(edge.source) ?? []) {
463
+ const next =
464
+ edge.sourceHandle == null || (isResolutionNode(graph.nodes[edge.source]) && edge.sourceHandle == 'pending')
465
+ ? route
466
+ : {
467
+ ...route,
468
+ [edge.source]:
469
+ graph.nodes[edge.source]?.kind == 'condition' &&
470
+ (graph.nodes[edge.source] as import('./change.ts').ConditionNode).matchMode == 'all' &&
471
+ edge.sourceHandle != otherwiseOutput
472
+ ? '$matched'
473
+ : edge.sourceHandle,
474
+ }
475
+ if (routes.some((known) => routeCovers(known, next))) continue
476
+ for (let index = routes.length - 1; index >= 0; index--) {
477
+ if (routeCovers(next, routes[index]!)) routes.splice(index, 1)
478
+ }
479
+ routes.push(next)
480
+ }
481
+ }
482
+ const nextRoutes = edges.length == 0 ? (isTriggerNode(node) ? [{ $trigger: id }] : [{}]) : routes
483
+ const previous = paths.get(id) ?? []
484
+ if (
485
+ parents.size != (ancestors.get(id)?.size ?? 0) ||
486
+ resolutions.size != (resolutionOutputs.get(id)?.size ?? 0) ||
487
+ nextRoutes.length != previous.length ||
488
+ nextRoutes.some((route) => !previous.some((known) => routeCovers(known, route) && routeCovers(route, known)))
489
+ )
490
+ changed = true
491
+ ancestors.set(id, parents)
492
+ resolutionOutputs.set(id, resolutions)
493
+ paths.set(id, nextRoutes)
494
+ }
495
+ }
496
+ const analysis = { ancestors, paths, resolutionOutputs }
497
+ pathsByGraph.set(graph, analysis)
498
+ return analysis
499
+ }
500
+
501
+ function sourcePaths(graph: Graph, paths: ReturnType<typeof graphPaths>['paths'], source: BindingSource | NodeSource) {
502
+ if (source.kind != 'node') return [{}]
503
+ const node = graph.nodes[source.nodeId]
504
+ const routes = paths.get(source.nodeId) ?? []
505
+ return node?.kind == 'condition' || (isResolutionNode(node) && source.output != 'pending')
506
+ ? routes.map((route) => ({ ...route, [source.nodeId]: source.output }))
507
+ : routes
508
+ }
509
+
510
+ function mappingAvailable(graph: Graph, target: string | undefined, mapping: InputMapping, analysis: ReturnType<typeof graphPaths>): boolean {
511
+ if (mapping.kind != 'sources') return true
512
+ const { ancestors, paths } = analysis
513
+ if (target != null && mapping.sources.some((source) => source.kind == 'node' && !ancestors.get(target)?.has(source.nodeId))) return false
514
+ if (
515
+ target != null &&
516
+ mapping.sources.some(
517
+ (source) =>
518
+ source.kind == 'node' &&
519
+ isResolutionNode(graph.nodes[source.nodeId]) &&
520
+ !analysis.resolutionOutputs.get(target)?.has(resolutionOutputKey(source.nodeId, source.output)),
521
+ )
522
+ )
523
+ return false
524
+ const sources = mapping.sources.map((source) => sourcePaths(graph, paths, source))
525
+ const targetPaths = target == null ? [{}] : (paths.get(target) ?? [])
526
+ // Sources may be absent, but must never overlap on the same execution path.
527
+ for (const [index, routes] of sources.entries()) {
528
+ for (const other of sources.slice(index + 1)) {
529
+ for (const left of routes) {
530
+ for (const targetRoute of targetPaths) {
531
+ if (!routesCompatible(left, targetRoute)) continue
532
+ const path = { ...targetRoute, ...left }
533
+ if (other.some((right) => routesCompatible(right, path))) return false
534
+ }
535
+ }
536
+ }
537
+ }
538
+ return true
539
+ }
540
+
541
+ export type InputSourceCheck =
542
+ | { readonly kind: 'available' }
543
+ | { readonly kind: 'source-missing' }
544
+ | { readonly kind: 'output-missing' }
545
+ | { readonly kind: 'field-missing' }
546
+ | { readonly kind: 'not-ready' }
547
+ | { readonly kind: 'schema'; readonly mismatch: SchemaMismatch }
548
+ | { readonly kind: 'schema-error' }
549
+
550
+ export type InputSourceCandidateCheck = Extract<InputSourceCheck, { readonly kind: 'available' | 'schema' | 'schema-error' }>
551
+
552
+ interface InputSourceFieldCandidate {
553
+ readonly system?: true
554
+ readonly description?: string
555
+ readonly output: string
556
+ readonly field: string
557
+ readonly check: InputSourceCandidateCheck
558
+ }
559
+
560
+ export interface InputSourceCandidate {
561
+ readonly system?: true
562
+ readonly description?: string
563
+ readonly field?: string
564
+ readonly fields?: readonly InputSourceFieldCandidate[]
565
+ readonly output: string
566
+ readonly check: InputSourceCandidateCheck
567
+ }
568
+
569
+ export interface InputSourcesCheck {
570
+ readonly conflict: boolean
571
+ readonly sources: readonly InputSourceCheck[]
572
+ }
573
+
574
+ function sourceCompatibility(source: PortDefinition, input: PortDefinition): InputSourceCandidateCheck {
575
+ const result = comparePorts(source, input)
576
+ if (result.kind == 'compatible') return { kind: 'available' }
577
+ return result.kind == 'incompatible' ? { kind: 'schema', mismatch: result.mismatch } : { kind: 'schema-error' }
578
+ }
579
+
580
+ /** Check one saved binding without enumerating candidate ports. */
581
+ export function checkInputSource(
582
+ graph: Graph,
583
+ target: string,
584
+ handle: string,
585
+ source: Pick<NodeSource, 'nodeId' | 'output' | 'field' | 'system'>,
586
+ ): InputSourceCheck {
587
+ const node = graph.nodes[source.nodeId]
588
+ const targetNode = graph.nodes[target]
589
+ if (node == null) return { kind: 'source-missing' }
590
+ if (targetNode == null) return { kind: 'not-ready' }
591
+ const output = nodeSourceOutputPorts(node, source.system)[source.output]
592
+ const input = nodeInputPorts(targetNode)[handle]
593
+ if (output == null) return { kind: 'output-missing' }
594
+ if (input == null) return { kind: 'not-ready' }
595
+ const analysis = graphPaths(graph)
596
+ if (analysis.ancestors.get(target)?.has(source.nodeId) !== true) return { kind: 'not-ready' }
597
+ if (isResolutionNode(node) && !analysis.resolutionOutputs.get(target)?.has(resolutionOutputKey(source.nodeId, source.output))) return { kind: 'not-ready' }
598
+ const selected = sourcePort(output, source.field)
599
+ if (selected == null) return { kind: 'field-missing' }
600
+ return sourceCompatibility(selected, input)
601
+ }
602
+
603
+ export function checkInputSources(
604
+ graph: Graph,
605
+ target: string,
606
+ handle: string,
607
+ sources: readonly Pick<NodeSource, 'nodeId' | 'output' | 'field' | 'system'>[],
608
+ ): InputSourcesCheck {
609
+ const checks = sources.map((source) => checkInputSource(graph, target, handle, source))
610
+ const conflict =
611
+ checks.length > 1 &&
612
+ checks.every((check) => check.kind == 'available') &&
613
+ !mappingAvailable(graph, target, { kind: 'sources', sources: sources.map((source) => ({ kind: 'node', ...source })) }, graphPaths(graph))
614
+ return { conflict, sources: checks }
615
+ }
616
+
617
+ /** Enumerate structurally available outputs while retaining their compatibility with one input. */
618
+ export function inputSourceCandidates(graph: Graph, target: string, handle: string): Readonly<Record<string, readonly InputSourceCandidate[]>> {
619
+ const targetNode = graph.nodes[target]
620
+ const input = targetNode == null ? undefined : nodeInputPorts(targetNode)[handle]
621
+ if (input == null) return {}
622
+ const analysis = graphPaths(graph)
623
+ return Object.fromEntries(
624
+ [...(analysis.ancestors.get(target) ?? [])].flatMap((nodeId) => {
625
+ const node = graph.nodes[nodeId]
626
+ if (node == null) return []
627
+ const ports = [
628
+ ...Object.entries(nodeSourceOutputPorts(node)).map(([output, definition]) => ({ output, definition, system: undefined })),
629
+ ...Object.entries(nodeSystemOutputPorts(node)).map(([output, definition]) => ({ output, definition, system: true as const })),
630
+ ]
631
+ const outputs = ports.flatMap(({ output, definition, system }) => {
632
+ if (!mappingAvailable(graph, target, { kind: 'sources', sources: [{ kind: 'node', nodeId, output, ...(system ? { system } : {}) }] }, analysis))
633
+ return []
634
+ const check = sourceCompatibility(definition, input)
635
+ const fields = sourceFields(definition).map(({ field, port }) =>
636
+ Object.assign(
637
+ { output, field, ...(system ? { system } : {}), check: sourceCompatibility(port, input) },
638
+ port.description == null ? {} : { description: port.description },
639
+ ),
640
+ )
641
+ return [
642
+ {
643
+ ...(system ? { system } : {}),
644
+ ...(definition.description == null ? {} : { description: definition.description }),
645
+ output,
646
+ check,
647
+ ...(fields.length > 0 ? { fields } : {}),
648
+ },
649
+ ]
650
+ })
651
+ return outputs.length == 0 ? [] : [[nodeId, outputs]]
652
+ }),
653
+ )
654
+ }
655
+
656
+ export function availableOutputs(graph: Graph, target: string, handle: string | undefined): Readonly<Record<string, readonly string[]>> {
657
+ const analysis = graphPaths(graph)
658
+ return Object.fromEntries(
659
+ [...(analysis.ancestors.get(target) ?? [])].flatMap((id) => {
660
+ const node = graph.nodes[id]
661
+ if (node == null) return []
662
+ const outputs = Object.keys(nodeOutputPorts(node)).filter(
663
+ (output) =>
664
+ mappingAvailable(graph, target, { kind: 'sources', sources: [{ kind: 'node', nodeId: id, output }] }, analysis) &&
665
+ (handle == null || portsAssignable(nodeOutputPorts(node)[output]!, nodeInputPorts(graph.nodes[target]!)[handle]!)),
666
+ )
667
+ return outputs.length == 0 ? [] : [[id, outputs]]
668
+ }),
669
+ )
670
+ }
671
+
672
+ function validateResolution(nodeId: string, node: Extract<GraphNode, { readonly kind: 'approval' | 'wait' }>, path: string, diagnostics: Diagnostic[]): void {
673
+ const fields = new Set(['description', 'icon', 'inputDefinitions', 'inputs', 'kind', 'maxExecutions', 'name', 'prompt'])
674
+ if (node.kind == 'wait') {
675
+ fields.add('autoContinue')
676
+ if (node.autoContinue != null && !waitAutoContinueSchema.safeParse(node.autoContinue).success)
677
+ diagnostics.push(
678
+ graphDiagnostic('wait.auto-continue-invalid', 'Enter a positive integer duration or a timestamp with an explicit timezone.', `${path}/autoContinue`),
679
+ )
680
+ }
681
+ const label = node.kind == 'wait' ? 'Wait' : 'Approval'
682
+ const unsupported = Object.keys(node).filter((field) => !fields.has(field))
683
+ if (unsupported.length > 0) {
684
+ diagnostics.push(
685
+ graphDiagnostic(`${node.kind}.field-unsupported`, `${label} node "${nodeId}" contains unsupported fields: ${unsupported.join(', ')}.`, path, {
686
+ fields: unsupported.join(', '),
687
+ nodeId,
688
+ }),
689
+ )
690
+ }
691
+ const names = node.inputDefinitions.map((port) => port.handle)
692
+ if (new Set(names).size != names.length)
693
+ diagnostics.push(graphDiagnostic(`${node.kind}.input-invalid`, `${label} input names must be unique.`, `${path}/inputDefinitions`))
694
+ if (typeof node.prompt != 'string' || node.prompt.trim().length == 0 || [...node.prompt].length > 1_000) {
695
+ diagnostics.push(graphDiagnostic(`${node.kind}.prompt-invalid`, `${label} prompt must contain between 1 and 1,000 Unicode code points.`, `${path}/prompt`))
696
+ }
697
+ }
698
+
699
+ function validateGraph(graph: Graph, document: FlowDocument, path: string, diagnostics: Diagnostic[]): void {
700
+ const analysis = graphPaths(graph)
701
+ const errorTriggers = Object.entries(graph.nodes).filter(([, node]) => node.kind == 'error')
702
+ if (errorTriggers.length > 1)
703
+ for (const [nodeId] of errorTriggers)
704
+ diagnostics.push(graphDiagnostic('graph.error-trigger-duplicate', 'A graph can contain only one Flow Error node.', `${path}/nodes/${nodeId}`))
705
+ const outputNodes = Object.entries(graph.nodes).filter(([, node]) => node.kind == 'outputs')
706
+ for (const [nodeId, node] of Object.entries(graph.nodes)) {
707
+ if (node.kind == 'webhook') diagnostics.push(...webhookResponseDiagnostics(graph, nodeId, `${path}/nodes/${nodeId}`))
708
+ if (node.kind === 'webhook-response') {
709
+ const status = node.inputs.statusCode
710
+ if (status?.kind === 'value' && !validWebhookStatus(status.value))
711
+ diagnostics.push(
712
+ graphDiagnostic(
713
+ 'webhook.response-status-invalid',
714
+ 'Use an integer status from 200 to 599, excluding 204, 205 and 304.',
715
+ `${path}/nodes/${nodeId}/inputs/statusCode`,
716
+ ),
717
+ )
718
+ const headers = node.inputs.headers
719
+ if (
720
+ headers?.kind === 'value' &&
721
+ (webhookHeaderValues(headers.value) == null || Object.values(headers.value as Record<string, unknown>).some((value) => typeof value !== 'string'))
722
+ )
723
+ diagnostics.push(
724
+ graphDiagnostic(
725
+ 'webhook.response-headers-invalid',
726
+ 'Use valid, unique HTTP header names and single-line text values.',
727
+ `${path}/nodes/${nodeId}/inputs/headers`,
728
+ ),
729
+ )
730
+ }
731
+ }
732
+ if (outputNodes.length > 1)
733
+ for (const [nodeId] of outputNodes)
734
+ diagnostics.push(graphDiagnostic('graph.outputs-duplicate', 'A graph can contain only one Outputs node.', `${path}/nodes/${nodeId}`))
735
+ const startTriggers = Object.entries(graph.nodes).filter(([, node]) => node.kind == 'manual' || node.kind == 'start')
736
+ if (startTriggers.length > 1) {
737
+ for (const [nodeId] of startTriggers) {
738
+ diagnostics.push(graphDiagnostic('graph.start-trigger-duplicate', 'A graph can contain only one Start Trigger.', `${path}/nodes/${nodeId}`))
739
+ }
740
+ }
741
+ const seen = new Set<string>()
742
+ for (const [index, edge] of graph.edges.entries()) {
743
+ const edgePath = `${path}/edges/${index}`
744
+ const source = graph.nodes[edge.source]
745
+ const target = graph.nodes[edge.target]
746
+ if (source == null || source.kind == 'outputs' || target == null || isTriggerNode(target)) {
747
+ diagnostics.push(graphDiagnostic('graph.edge-invalid', 'Execution edges require an existing source and an executable target.', edgePath))
748
+ } else if (source.kind == 'condition' || isResolutionNode(source)) {
749
+ const exits = isResolutionNode(source) ? Object.keys(resolutionOutputPorts(source)) : [...source.cases.map((item) => item.output), otherwiseOutput]
750
+ if (edge.sourceHandle == null || !exits.some((exit) => exit == edge.sourceHandle)) {
751
+ diagnostics.push(graphDiagnostic('graph.edge-invalid', 'Choose a declared execution branch.', edgePath))
752
+ }
753
+ } else if (edge.sourceHandle != null) {
754
+ diagnostics.push(graphDiagnostic('graph.edge-invalid', 'Ordinary execution edges do not select a data output.', edgePath))
755
+ }
756
+ const key = JSON.stringify([edge.source, edge.sourceHandle, edge.target])
757
+ if (seen.has(key)) diagnostics.push(graphDiagnostic('graph.edge-duplicate', 'Execution edge is duplicated.', edgePath))
758
+ seen.add(key)
759
+ }
760
+ for (const [nodeId, node] of Object.entries(graph.nodes)) {
761
+ const nodePath = `${path}/nodes/${nodeId}`
762
+ if (!isTriggerNode(node) && node.maxExecutions != null && (!Number.isSafeInteger(node.maxExecutions) || node.maxExecutions < 1)) {
763
+ diagnostics.push(graphDiagnostic('node.max-executions-invalid', 'Maximum executions must be a positive safe integer.', `${nodePath}/maxExecutions`))
764
+ }
765
+ if (node.onError != null && (!supportsNodeErrorHandling(node) || !['stop', 'error'].includes(node.onError)))
766
+ diagnostics.push(graphDiagnostic('node.error-handling-invalid', 'This node does not support this error handling setting.', `${nodePath}/onError`))
767
+ if ('retry' in node && node.retry != null) {
768
+ if (!supportsNodeRetry(node)) diagnostics.push(graphDiagnostic('node.retry-unsupported', 'This node does not support retries.', `${nodePath}/retry`))
769
+ if (!retryConfigurationSchema.safeParse(node.retry).success)
770
+ diagnostics.push(graphDiagnostic('node.retry-invalid', 'Retries must be 0–5 with an interval of 1–60 whole seconds.', `${nodePath}/retry`))
771
+ }
772
+ if (isTriggerNode(node)) {
773
+ validateTrigger(nodeId, node, nodePath, diagnostics)
774
+ continue
775
+ }
776
+ for (const [handle, mapping] of Object.entries(nodeInputMappings(node))) {
777
+ if (!mappingAvailable(graph, nodeId, mapping, analysis))
778
+ diagnostics.push(
779
+ graphDiagnostic(
780
+ 'graph.source-unavailable',
781
+ 'Input sources must reference execution ancestors, respect pending Wait decisions, and never provide multiple values on the same path.',
782
+ node.kind == 'outputs' ? `${nodePath}/outputMappings/${handle.replaceAll('~', '~0').replaceAll('/', '~1')}` : `${nodePath}/inputs/${handle}`,
783
+ ),
784
+ )
785
+ }
786
+ const inputPorts = nodeInputPorts(node)
787
+ if (node.kind == 'task') {
788
+ const ports = [...node.task.inputs, ...(node.additionalInputs ?? [])]
789
+ const handles = new Set<string>()
790
+ for (const port of ports) {
791
+ if (!('handle' in port)) continue
792
+ if (handles.has(port.handle)) {
793
+ diagnostics.push(
794
+ graphDiagnostic('graph.input-duplicate', `Node "${nodeId}" declares input "${port.handle}" more than once.`, nodePath, {
795
+ handle: port.handle,
796
+ nodeId,
797
+ }),
798
+ )
799
+ }
800
+ handles.add(port.handle)
801
+ }
802
+ }
803
+ const ports = [...Object.values(inputPorts), ...Object.values(nodeOutputPorts(node))]
804
+ if (ports.some((port) => hasRetiredRef(port.jsonSchema))) {
805
+ diagnostics.push(graphDiagnostic('graph.schema-unsupported', 'Runtime Ref schemas are not supported.', nodePath))
806
+ }
807
+ const inputs = new Set(Object.keys(inputPorts))
808
+ for (const [handle, mapping] of Object.entries(nodeInputMappings(node))) {
809
+ const mappingPath =
810
+ node.kind == 'outputs' ? `${nodePath}/outputMappings/${handle.replaceAll('~', '~0').replaceAll('/', '~1')}` : `${nodePath}/inputs/${handle}`
811
+ if (!inputs.has(handle)) {
812
+ diagnostics.push(graphDiagnostic('graph.input-missing', `Node "${nodeId}" does not expose input "${handle}".`, mappingPath, { handle, nodeId }))
813
+ }
814
+ if (mapping.kind == 'sources') {
815
+ const variableSources = mapping.sources.filter((source) => source.kind == 'binding' && document.bindings[source.bindingId]?.kind == 'variable')
816
+ if (variableSources.length > 0 && (variableSources.length != 1 || mapping.sources.length != 1)) {
817
+ diagnostics.push(graphDiagnostic('graph.variable-source-mixed', 'A variable must be the only source for an input.', mappingPath))
818
+ }
819
+ for (const source of mapping.sources) checkSource(source, graph, document, inputPorts[handle], mappingPath, diagnostics)
820
+ }
821
+ }
822
+ if (isResolutionNode(node)) validateResolution(nodeId, node, nodePath, diagnostics)
823
+ if (node.kind != 'condition') continue
824
+ if (Object.keys(node.inputs).length > 0)
825
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Condition cannot declare node inputs.', nodePath, { variant: 'inputs' }))
826
+ const outputs = new Set<string>([otherwiseOutput])
827
+ for (const [index, condition] of node.cases.entries()) {
828
+ const casePath = `${nodePath}/cases/${index}`
829
+ if (condition.output.trim().length == 0 || condition.output == '__proto__' || outputs.has(condition.output))
830
+ diagnostics.push(
831
+ graphDiagnostic('condition.invalid', 'Case output must be nonempty, unique, and not reserved.', `${casePath}/output`, { variant: 'output' }),
832
+ )
833
+ outputs.add(condition.output)
834
+ if (condition.groups.length == 0)
835
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Case requires at least one group.', casePath, { variant: 'case' }))
836
+ for (const [g, group] of condition.groups.entries()) {
837
+ const groupPath = `${casePath}/groups/${g}`
838
+ if (group.expressions.length == 0)
839
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Group requires at least one expression.', groupPath, { variant: 'group' }))
840
+ for (const [e, expression] of group.expressions.entries()) {
841
+ const expressionPath = `${groupPath}/expressions/${e}`
842
+ const type = (operand: typeof expression.left | undefined): string | undefined => {
843
+ if (operand == null || (operand.kind == 'value' && operand.value === undefined)) {
844
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Condition operand is incomplete.', expressionPath, { variant: 'operand' }))
845
+ return
846
+ }
847
+ if (operand.kind == 'value') {
848
+ if (operand.jsonSchema != null && !matchesSchema(operand.value!, operand.jsonSchema))
849
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Operand does not match its declared schema.', expressionPath, { variant: 'schema' }))
850
+ return valueType(operand.value!)
851
+ }
852
+ if (operand.source.kind === 'binding') return 'string'
853
+ const port = checkSource(operand.source, graph, document, undefined, expressionPath, [])
854
+ const raw = schemaObject(port?.jsonSchema ?? {})?.type
855
+ return typeof raw == 'string' ? raw : undefined
856
+ }
857
+ const left = type(expression.left)
858
+ const right = unaryOperator(expression.operator) ? undefined : type(expression.right)
859
+ if (unaryOperator(expression.operator) && expression.right != null)
860
+ diagnostics.push(graphDiagnostic('condition.invalid', 'Unary operators cannot have a right operand.', expressionPath, { variant: 'unary' }))
861
+ const issue = comparisonIssue(expression.operator, left, right)
862
+ if (issue != null) diagnostics.push(graphDiagnostic('condition.invalid', issue.message, expressionPath, { variant: 'type' }))
863
+ }
864
+ }
865
+ }
866
+ }
867
+ }
868
+
869
+ export function validateFlowGraph(revision: RevisionContent): readonly Diagnostic[] {
870
+ const diagnostics: Diagnostic[] = []
871
+ for (const [id, node] of Object.entries(revision.document.graph.nodes)) {
872
+ if (node.kind != 'start' && node.kind != 'outputs') continue
873
+ const field = node.kind == 'start' ? 'inputDefs' : 'outputDefs'
874
+ const ports = node.kind == 'start' ? node.inputDefs : node.outputDefs
875
+ const path = `/document/graph/nodes/${id.replaceAll('~', '~0').replaceAll('/', '~1')}/${field}`
876
+ if (new Set(ports.map((port) => port.handle)).size != ports.length)
877
+ diagnostics.push(graphDiagnostic('graph.port-duplicate', 'Workflow interface names must be unique.', path))
878
+ if (ports.some((port) => hasRetiredRef(port.jsonSchema)))
879
+ diagnostics.push(graphDiagnostic('graph.schema-unsupported', 'Runtime Ref schemas are not supported.', path))
880
+ }
881
+ for (const [bindingId, binding] of Object.entries(revision.document.bindings)) {
882
+ if (binding.kind == 'variable' && !validVariableName(binding.target)) {
883
+ diagnostics.push(
884
+ graphDiagnostic('binding.variable-invalid', `Variable binding "${bindingId}" has an invalid target.`, `/document/bindings/${bindingId}/target`, {
885
+ bindingId,
886
+ }),
887
+ )
888
+ }
889
+ }
890
+ validateGraph(revision.document.graph, revision.document, '/document/graph', diagnostics)
891
+ return diagnostics
892
+ }