@uipath/maestro-builder-sdk 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/dist/bindings-v2.d.ts +37 -0
  4. package/dist/bindings-v2.js +155 -0
  5. package/dist/bindings.d.ts +6 -0
  6. package/dist/bindings.js +6 -0
  7. package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
  8. package/dist/bpmn/bpmn-expr-check.js +218 -0
  9. package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
  10. package/dist/bpmn/bpmn-sdk.js +1476 -0
  11. package/dist/bpmn/check-cli.d.ts +3 -0
  12. package/dist/bpmn/check-cli.js +32 -0
  13. package/dist/bpmn/check.d.ts +25 -0
  14. package/dist/bpmn/check.js +430 -0
  15. package/dist/bpmn/compile-cli.d.ts +3 -0
  16. package/dist/bpmn/compile-cli.js +60 -0
  17. package/dist/bpmn/connector.d.ts +56 -0
  18. package/dist/bpmn/connector.js +199 -0
  19. package/dist/bpmn/decompile-cli.d.ts +8 -0
  20. package/dist/bpmn/decompile-cli.js +54 -0
  21. package/dist/bpmn/decompile.d.ts +6 -0
  22. package/dist/bpmn/decompile.js +1108 -0
  23. package/dist/bpmn/format-profile.d.ts +27 -0
  24. package/dist/bpmn/format-profile.js +11 -0
  25. package/dist/bpmn/intsvc.d.ts +77 -0
  26. package/dist/bpmn/intsvc.js +155 -0
  27. package/dist/bpmn/load.d.ts +4 -0
  28. package/dist/bpmn/load.js +22 -0
  29. package/dist/bpmn/merge-cli.d.ts +3 -0
  30. package/dist/bpmn/merge-cli.js +45 -0
  31. package/dist/bpmn/merge.d.ts +19 -0
  32. package/dist/bpmn/merge.js +293 -0
  33. package/dist/bpmn/registry-types.json +1869 -0
  34. package/dist/bpmn/serialize.d.ts +28 -0
  35. package/dist/bpmn/serialize.js +757 -0
  36. package/dist/bpmn/tidy-cli.d.ts +2 -0
  37. package/dist/bpmn/tidy-cli.js +39 -0
  38. package/dist/bpmn/typed-node.d.ts +382 -0
  39. package/dist/bpmn/typed-node.js +551 -0
  40. package/dist/bpmn/uipath-moddle.v1.json +823 -0
  41. package/dist/bpmn/xml-ranges.d.ts +45 -0
  42. package/dist/bpmn/xml-ranges.js +151 -0
  43. package/dist/case/case-expr-check.d.ts +35 -0
  44. package/dist/case/case-expr-check.js +85 -0
  45. package/dist/case/case-sdk.d.ts +1282 -0
  46. package/dist/case/case-sdk.js +923 -0
  47. package/dist/case/check-cli.d.ts +3 -0
  48. package/dist/case/check-cli.js +31 -0
  49. package/dist/case/check.d.ts +18 -0
  50. package/dist/case/check.js +456 -0
  51. package/dist/case/compile-cli.d.ts +3 -0
  52. package/dist/case/compile-cli.js +291 -0
  53. package/dist/case/decompile-cli.d.ts +9 -0
  54. package/dist/case/decompile-cli.js +117 -0
  55. package/dist/case/decompile.d.ts +187 -0
  56. package/dist/case/decompile.js +1170 -0
  57. package/dist/case/entry-points.d.ts +79 -0
  58. package/dist/case/entry-points.js +118 -0
  59. package/dist/case/format-profile.d.ts +60 -0
  60. package/dist/case/format-profile.js +79 -0
  61. package/dist/case/ids.d.ts +24 -0
  62. package/dist/case/ids.js +76 -0
  63. package/dist/case/load.d.ts +12 -0
  64. package/dist/case/load.js +32 -0
  65. package/dist/case/preserve.d.ts +133 -0
  66. package/dist/case/preserve.js +105 -0
  67. package/dist/case/serialize.d.ts +10 -0
  68. package/dist/case/serialize.js +1414 -0
  69. package/dist/check-cli.d.ts +3 -0
  70. package/dist/check-cli.js +118 -0
  71. package/dist/check.d.ts +146 -0
  72. package/dist/check.js +4898 -0
  73. package/dist/cli/commands/bpmn.d.ts +2 -0
  74. package/dist/cli/commands/bpmn.js +73 -0
  75. package/dist/cli/commands/case.d.ts +2 -0
  76. package/dist/cli/commands/case.js +60 -0
  77. package/dist/cli/commands/flow.d.ts +2 -0
  78. package/dist/cli/commands/flow.js +99 -0
  79. package/dist/cli/commands/registry.d.ts +10 -0
  80. package/dist/cli/commands/registry.js +170 -0
  81. package/dist/cli/common.d.ts +30 -0
  82. package/dist/cli/common.js +92 -0
  83. package/dist/cli/index.d.ts +2 -0
  84. package/dist/cli/index.js +18 -0
  85. package/dist/cli-run.d.ts +2 -0
  86. package/dist/cli-run.js +42 -0
  87. package/dist/compile-cli.d.ts +3 -0
  88. package/dist/compile-cli.js +160 -0
  89. package/dist/config.d.ts +47 -0
  90. package/dist/config.js +201 -0
  91. package/dist/core/actions.d.ts +3244 -0
  92. package/dist/core/actions.js +1062 -0
  93. package/dist/core/binding-messages.d.ts +1 -0
  94. package/dist/core/binding-messages.js +16 -0
  95. package/dist/core/bindings.d.ts +12 -0
  96. package/dist/core/bindings.js +67 -0
  97. package/dist/core/ceql.d.ts +73 -0
  98. package/dist/core/ceql.js +272 -0
  99. package/dist/core/cli-spelling.d.ts +39 -0
  100. package/dist/core/cli-spelling.js +62 -0
  101. package/dist/core/connector-raw-node.d.ts +21 -0
  102. package/dist/core/connector-raw-node.js +47 -0
  103. package/dist/core/connectors.d.ts +184 -0
  104. package/dist/core/connectors.js +128 -0
  105. package/dist/core/expr-check.d.ts +93 -0
  106. package/dist/core/expr-check.js +186 -0
  107. package/dist/core/expr.d.ts +323 -0
  108. package/dist/core/expr.js +400 -0
  109. package/dist/core/index.d.ts +10 -0
  110. package/dist/core/index.js +10 -0
  111. package/dist/core/library.d.ts +440 -0
  112. package/dist/core/library.js +1183 -0
  113. package/dist/core/list-envelope.d.ts +45 -0
  114. package/dist/core/list-envelope.js +97 -0
  115. package/dist/core/lookup-store.d.ts +22 -0
  116. package/dist/core/lookup-store.js +85 -0
  117. package/dist/core/lookups.d.ts +227 -0
  118. package/dist/core/lookups.js +231 -0
  119. package/dist/core/node-classes.d.ts +234 -0
  120. package/dist/core/node-classes.js +158 -0
  121. package/dist/core/script-return.d.ts +68 -0
  122. package/dist/core/script-return.js +197 -0
  123. package/dist/core/stable-id.d.ts +28 -0
  124. package/dist/core/stable-id.js +37 -0
  125. package/dist/core-definitions.json +16249 -0
  126. package/dist/decompile-cli.d.ts +3 -0
  127. package/dist/decompile-cli.js +131 -0
  128. package/dist/decompile.d.ts +138 -0
  129. package/dist/decompile.js +2482 -0
  130. package/dist/emit.d.ts +10 -0
  131. package/dist/emit.js +60 -0
  132. package/dist/event-filters.d.ts +130 -0
  133. package/dist/event-filters.js +137 -0
  134. package/dist/flow-expr-check.d.ts +17 -0
  135. package/dist/flow-expr-check.js +193 -0
  136. package/dist/flow-sdk.d.ts +1341 -0
  137. package/dist/flow-sdk.js +1164 -0
  138. package/dist/flow-variables.d.ts +13 -0
  139. package/dist/flow-variables.js +42 -0
  140. package/dist/format-profile.d.ts +72 -0
  141. package/dist/format-profile.js +97 -0
  142. package/dist/generators/_connections.py +287 -0
  143. package/dist/generators/_events.py +355 -0
  144. package/dist/generators/_lookups.py +114 -0
  145. package/dist/generators/_naming.py +185 -0
  146. package/dist/generators/_objects.py +299 -0
  147. package/dist/generators/_resolve.py +273 -0
  148. package/dist/generators/convert_library_to_md.py +1441 -0
  149. package/dist/generators/extract_library.py +125 -0
  150. package/dist/generators/generate_connectors_ts.py +936 -0
  151. package/dist/generators/generate_library_json.py +2527 -0
  152. package/dist/generators/generate_triggers_ts.py +110 -0
  153. package/dist/generators/materialize_registry.py +155 -0
  154. package/dist/generators/prepare_connector.py +1688 -0
  155. package/dist/index.d.ts +35 -0
  156. package/dist/index.js +35 -0
  157. package/dist/ixp-definition.json +1982 -0
  158. package/dist/library.d.ts +6 -0
  159. package/dist/library.js +6 -0
  160. package/dist/load.d.ts +16 -0
  161. package/dist/load.js +95 -0
  162. package/dist/merge-cli.d.ts +3 -0
  163. package/dist/merge-cli.js +47 -0
  164. package/dist/merge.d.ts +125 -0
  165. package/dist/merge.js +0 -0
  166. package/dist/migrate.d.ts +15 -0
  167. package/dist/migrate.js +245 -0
  168. package/dist/node-runtime.d.ts +3 -0
  169. package/dist/node-runtime.js +22 -0
  170. package/dist/package-name.d.ts +17 -0
  171. package/dist/package-name.js +17 -0
  172. package/dist/ref-resolve.d.ts +77 -0
  173. package/dist/ref-resolve.js +170 -0
  174. package/dist/registry/cache.d.ts +55 -0
  175. package/dist/registry/cache.js +134 -0
  176. package/dist/registry/curated-overlay.d.ts +16 -0
  177. package/dist/registry/curated-overlay.js +62 -0
  178. package/dist/registry/extractor.d.ts +22 -0
  179. package/dist/registry/extractor.js +18 -0
  180. package/dist/registry/materialize-bin.d.ts +2 -0
  181. package/dist/registry/materialize-bin.js +7 -0
  182. package/dist/registry/materialize-cli.d.ts +1 -0
  183. package/dist/registry/materialize-cli.js +46 -0
  184. package/dist/registry/materializer.d.ts +23 -0
  185. package/dist/registry/materializer.js +34 -0
  186. package/dist/registry/path-cli.d.ts +1 -0
  187. package/dist/registry/path-cli.js +56 -0
  188. package/dist/registry/prepare-cli.d.ts +62 -0
  189. package/dist/registry/prepare-cli.js +185 -0
  190. package/dist/registry/pull-cli.d.ts +1 -0
  191. package/dist/registry/pull-cli.js +274 -0
  192. package/dist/registry/python.d.ts +26 -0
  193. package/dist/registry/python.js +46 -0
  194. package/dist/registry/search-cli.d.ts +52 -0
  195. package/dist/registry/search-cli.js +159 -0
  196. package/dist/schema-version.d.ts +165 -0
  197. package/dist/schema-version.js +148 -0
  198. package/dist/serialize.d.ts +64 -0
  199. package/dist/serialize.js +6398 -0
  200. package/lib/README.md +112 -0
  201. package/lib/library-json.samples/index.json +158 -0
  202. package/lib/library-json.samples/invariant-collections.json +83 -0
  203. package/lib/library-json.samples/list-envelopes.json +123 -0
  204. package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
  205. package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
  206. package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
  207. package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
  208. package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
  209. package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
  210. package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
  211. package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
  212. package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
  213. package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
  214. package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
  215. package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
  216. package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
  217. package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
  218. package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
  219. package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
  220. package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
  221. package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
  222. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
  223. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
  224. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
  225. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
  226. package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
  227. package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
  228. package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
  229. package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
  230. package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
  231. package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
  232. package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
  233. package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
  234. package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
  235. package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
  236. package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
  237. package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
  238. package/package.json +85 -0
@@ -0,0 +1,1164 @@
1
+ /**
2
+ * flow-sdk — a small fluent builder for UiPath Flow workflows.
3
+ *
4
+ * You author a workflow as TypeScript code that *constructs* a graph of nodes
5
+ * and edges. Calling `.build()` returns a plain description of that graph; the
6
+ * `compile` tool serializes it into a real `.flow` JSON file.
7
+ *
8
+ * This is deliberately a builder, not a program: control flow (branch/loop) is
9
+ * expressed by calling `.branch()` / `.loop()`, not with native `if`/`for`.
10
+ * Data references are expressed with the expression helpers (`input`, `out`,
11
+ * `v`, `js`, `tmpl`) rather than by reading real values — nothing actually runs
12
+ * at build time except the graph construction.
13
+ *
14
+ * The expression helpers and action specs are shared across all builder SDKs
15
+ * and live in `core/`; this module re-exports them so the historical
16
+ * `flow-sdk.js` authoring surface is unchanged, and adds the Flow-specific
17
+ * builder (steps, branches, loops, return).
18
+ */
19
+ // Re-export the shared core authoring surface (Expr, lit/v/input/out/js/tmpl,
20
+ // types, and http/script/connector action specs) so `./flow-sdk.js` remains the
21
+ // single import an author needs.
22
+ export * from './core/expr.js';
23
+ export * from './core/actions.js';
24
+ // `$ref`-split ingestion (design §5): decompile resolves local-file refs into
25
+ // one object model. Exported so a host doing its own file reading can too.
26
+ export { resolveFlowRefs, resolveRefPath, hasFileRefs } from './ref-resolve.js';
27
+ export * from './core/connectors.js';
28
+ export * from './core/lookups.js';
29
+ export * from './core/node-classes.js';
30
+ import { err as errRef, toExpr, types } from './core/expr.js';
31
+ import { builtinActionSpec, builtinTriggerSpec, subscriptionFrom } from './core/actions.js';
32
+ import { checkFlowExpressions, FlowBuildError } from './flow-expr-check.js';
33
+ import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
34
+ import { schemaVersionRefusal } from './schema-version.js';
35
+ export { FlowBuildError, checkFlowExpressions } from './flow-expr-check.js';
36
+ const TYPE_NAMES = Object.values(types);
37
+ /**
38
+ * Reject a `.input()` / `.output()` argument that is not a `{ name: type }` map.
39
+ *
40
+ * WHY THIS THROWS INSTEAD OF DIAGNOSING. TypeScript's types are erased at run
41
+ * time, so `.output('title', 'string')` — the object-shaped API called as if it
42
+ * took two positional arguments — reaches the builder as the plain string
43
+ * `'title'`. `Object.entries('title')` is `[['0','t'],['1','i'],…]`, so the flow
44
+ * quietly declared five globals named `"0".."4"` with types `t,i,t,l,e`.
45
+ * The source checker said "no issues" (nothing it looks at was wrong — the names and types
46
+ * were merely nonsense), and the mistake only surfaced further down the ladder, about
47
+ * a file the author never wrote.
48
+ *
49
+ * This is the same class as `.branch('big','amount')` (card T1) and the same fix
50
+ * shape: normalize or reject at the builder, where the mistake is. `.input()` and
51
+ * `.output()` run while the module is being evaluated, so the throw arrives before
52
+ * anything else — with the call that caused it named.
53
+ */
54
+ function assertShape(method, shape) {
55
+ if (typeof shape !== 'object' || shape === null || Array.isArray(shape)) {
56
+ throw new TypeError(`.${method}() takes ONE object mapping names to types — got ${Array.isArray(shape) ? 'an array' : typeof shape}`
57
+ + `${typeof shape === 'string' ? ` (${JSON.stringify(shape)})` : ''}. `
58
+ + `Write .${method}({ ${typeof shape === 'string' ? shape : 'name'}: types.string }), not `
59
+ + `.${method}('name', 'string'): a string would be iterated character by character and `
60
+ + `declare one garbage variable per letter.`);
61
+ }
62
+ return shape;
63
+ }
64
+ /**
65
+ * Normalize a `TypeDesc | VarSpec` declaration value into VarDecl fields.
66
+ * Outputs reject `default` — the format only persists defaults on `in`/`inout`.
67
+ */
68
+ function varDeclOf(method, name, value) {
69
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
70
+ const spec = value;
71
+ assertType(method, name, spec.type);
72
+ if (method === 'output' && spec.default !== undefined) {
73
+ throw new TypeError(`.output() declared ${JSON.stringify(name)} with a default, but the .flow format only persists `
74
+ + `defaults on inputs and vars. Bind the value with .return() instead.`);
75
+ }
76
+ return {
77
+ type: spec.type,
78
+ ...(spec.default !== undefined ? { default: spec.default } : {}),
79
+ ...(spec.description !== undefined ? { description: spec.description } : {}),
80
+ ...(spec.subType !== undefined ? { subType: spec.subType } : {}),
81
+ ...(spec.schema !== undefined ? { schema: spec.schema } : {}),
82
+ };
83
+ }
84
+ assertType(method, name, value);
85
+ return { type: value };
86
+ }
87
+ /** A declared variable's type must be one of the seven `types.*` names. */
88
+ function assertType(method, name, type) {
89
+ if (typeof type === 'string' && TYPE_NAMES.includes(type))
90
+ return;
91
+ throw new TypeError(`.${method}() declared ${JSON.stringify(name)} with type ${JSON.stringify(type)}, which is not `
92
+ + `a type. Use one of ${TYPE_NAMES.map((t) => `types.${t}`).join(', ')}.`);
93
+ }
94
+ // ─── Triggers (what starts the flow) ────────────────────────────────────────
95
+ //
96
+ // Every flow starts at one trigger node. Omit `.trigger(...)` and you get the
97
+ // manual trigger — started by a caller, on demand — which is what all the
98
+ // examples do and stays the default.
99
+ //
100
+ // `scheduled(...)` is the other start trigger: the platform fires the flow on a
101
+ // recurring interval, with no caller. Note what that means for a LOCAL run —
102
+ // the interval is packaged into the flow's BPMN as a timer expression, and the
103
+ // thing that reads it and fires is the platform's scheduler. Nothing in this
104
+ // repo's ladder runs a timer, so locally a scheduled flow starts exactly like a
105
+ // manual one; what we can verify here is that the emitted node is the right
106
+ // node carrying the right interval.
107
+ /**
108
+ * The eight intervals the designer offers in its Frequency dropdown. Any other
109
+ * valid
110
+ * {@link https://docs.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm | ISO-8601 repeating interval}
111
+ * works too (see `ScheduleEvery`) — these are
112
+ * just the ones that round-trip as a named preset rather than as "custom".
113
+ */
114
+ export const SCHEDULE_PRESETS = [
115
+ 'R/PT5M', 'R/PT15M', 'R/PT30M', 'R/PT1H', 'R/PT6H', 'R/PT12H', 'R/P1D', 'R/P1W',
116
+ ];
117
+ /**
118
+ * The manual trigger, explicitly. Omitting `.trigger(...)` means exactly this —
119
+ * the factory exists so decompiled source can carry the trigger's exact
120
+ * definition version (`.trigger(manual(), { version: '1.0.0' })`) instead of
121
+ * leaving the default implicit, and so hand-written flows can opt into the
122
+ * same explicitness.
123
+ *
124
+ * @returns A trigger spec to pass to `.trigger(...)`.
125
+ */
126
+ export function manual() {
127
+ return builtinTriggerSpec({ kind: 'manual' });
128
+ }
129
+ export function onEvent(a, opts) {
130
+ return builtinTriggerSpec({ kind: 'event', subscription: subscriptionFrom(a, opts) });
131
+ }
132
+ /**
133
+ * Start the flow on a recurring schedule instead of on demand.
134
+ *
135
+ * @remarks
136
+ * Field shapes, the interval format, and what a local run does and does not
137
+ * prove: `references/scheduled-trigger.md`.
138
+ *
139
+ * @example
140
+ * **Run a flow daily**
141
+ * ```ts
142
+ * export default flow('nightly-rollup')
143
+ * .trigger(scheduled({ every: 'R/P1D' }))
144
+ * .step('rollup', script({ code: 'return { ok: true };' }))
145
+ * .build();
146
+ * ```
147
+ *
148
+ * @param inputs - The recurring interval, e.g. `{ every: 'R/PT1H' }`.
149
+ * @returns A trigger spec to pass to `.trigger(...)`.
150
+ */
151
+ export function scheduled(inputs) {
152
+ return builtinTriggerSpec({ kind: 'scheduled', inputs });
153
+ }
154
+ /**
155
+ * Start the flow from a FORM a person fills in (`core.trigger.form`) — the
156
+ * platform renders one field per flow input and the submitted values ARE the
157
+ * flow's inputs.
158
+ *
159
+ * ```ts
160
+ * export default flow('expense-request')
161
+ * .input({ amount: types.number, reason: types.string })
162
+ * .trigger(formTrigger())
163
+ * .step('review', hitl({ … }))
164
+ * .build();
165
+ * ```
166
+ *
167
+ * The form's fields are DERIVED from `.input()` — one per input, the same rule
168
+ * the designer's own form-trigger editor applies to a flow's arguments: the
169
+ * field id is the input's name (so submitted values line up by name), the label
170
+ * is the name sentence-cased, the field type follows the input's type, and a
171
+ * field is required exactly when its input declares no default.
172
+ *
173
+ * The definition is BUNDLED from the workbench manifest — the tenant registry
174
+ * does not serve the family (same availability note as do-while).
175
+ *
176
+ * No local rung renders a form: `--input` supplies the values locally, and a
177
+ * green ladder proves the trigger type, the derived schema, and that the graph
178
+ * runs — not that a person saw a form.
179
+ *
180
+ * @returns A trigger spec to pass to `.trigger(...)`.
181
+ */
182
+ export function formTrigger() {
183
+ return builtinTriggerSpec({ kind: 'form' });
184
+ }
185
+ /**
186
+ * Start the flow when a new CONVERSATION is created
187
+ * (`core.trigger.conversation`) — the entry point for a chat-driven process.
188
+ *
189
+ * ```ts
190
+ * export default flow('support-chat')
191
+ * .trigger(conversationTrigger())
192
+ * .step('listen', waitForMessage({ conversationId: out('start', 'conversationId') }))
193
+ * .step('answer', sendMessage({
194
+ * conversationId: out('start', 'conversationId'),
195
+ * exchangeId: out('listen', 'conversationContext.latestExchangeId'),
196
+ * content: 'Looking into it.',
197
+ * }))
198
+ * .build();
199
+ * ```
200
+ *
201
+ * The trigger publishes the conversation it started —
202
+ * `out('start', 'conversationId')` — which is what every conversational step
203
+ * is keyed by. Nothing else is authored on it.
204
+ *
205
+ * The definition is BUNDLED from the workbench manifest: the family is
206
+ * `AvailableOnTenant: false` today, so a flow compiles and validates offline
207
+ * while the platform catches up (the do-while precedent).
208
+ *
209
+ * @returns A trigger spec to pass to `.trigger(...)`.
210
+ */
211
+ export function conversationTrigger() {
212
+ return builtinTriggerSpec({ kind: 'conversation' });
213
+ }
214
+ /**
215
+ * Start the flow when a phone call comes IN (`core.trigger.voice`).
216
+ *
217
+ * ```ts
218
+ * export default flow('support-line')
219
+ * .trigger(voiceTrigger())
220
+ * .step('greet', voiceAgent({
221
+ * systemPrompt: 'Greet {{input.customerName}} and find out why they called.',
222
+ * inputs: { customerName: input('customerName') },
223
+ * callContext: out('start', 'callContext'),
224
+ * }))
225
+ * .step('hangUp', endCall({ callContext: out('start', 'callContext') }))
226
+ * .build();
227
+ * ```
228
+ *
229
+ * The trigger publishes the live call as `out('start', 'callContext')` — the
230
+ * object (`{ type, id, conversationId, … }`) every voice step is keyed by.
231
+ * Nothing else is authored on it.
232
+ *
233
+ * The definition is BUNDLED from the workbench manifest: the family is
234
+ * `AvailableOnTenant: false` today (the do-while precedent).
235
+ *
236
+ * @returns A trigger spec to pass to `.trigger(...)`.
237
+ */
238
+ export function voiceTrigger() {
239
+ return builtinTriggerSpec({ kind: 'voice' });
240
+ }
241
+ /**
242
+ * The designer's sticky-note palette. Any other string is passed through — the
243
+ * `.flow` format stores `inputs.color` unvalidated — but these five are what the
244
+ * canvas offers and what round-trips as a named swatch.
245
+ */
246
+ export const STICKY_NOTE_COLORS = ['yellow', 'pink', 'blue', 'green', 'white'];
247
+ // ─── Builder ────────────────────────────────────────────────────────────────
248
+ /** Collects a sequence of steps. Used for the flow body and each branch/loop arm. */
249
+ class StepList {
250
+ steps = [];
251
+ /**
252
+ * The step whose failure led here, set only on the sub-builder `.onError(...)`
253
+ * hands its callback. `err()` on this builder needs no step name because of it.
254
+ */
255
+ errorSource;
256
+ /**
257
+ * Read the failure that led into this handler → `err('<the failed step>', field)`.
258
+ *
259
+ * @remarks
260
+ * Only available on the sub-builder `.onError(...)` / `.stepToList('error', …)`
261
+ * passes to its callback, because only there is "the step that failed" a fact
262
+ * rather than a guess.
263
+ *
264
+ * Prefer it over the free `err('<step>', field)` inside a handler. It cannot
265
+ * name the wrong step, it cannot drift when the step is renamed, and — the
266
+ * reason it exists — it keeps the envelope's LOCATION out of author-visible
267
+ * code entirely. Which variable carries the envelope is per node family
268
+ * (measured: `<step>.error` on connectors, script, deep-rag, subflow and
269
+ * queue; `<step>.output` on managed http), and the compiler resolves it.
270
+ *
271
+ * ```ts
272
+ * .step('load', script({ code: 'return JSON.parse($vars.start.output.raw);' }))
273
+ * .onError((h) => h.return({ note: tmpl`bad payload: ${h.err('message')}` }))
274
+ * ```
275
+ *
276
+ * @param field - One of the envelope's fields. Omit it to test WHETHER the
277
+ * step failed (the boolean the platform sets when the boundary fires).
278
+ * @returns An {@link Expr} reading the failure of the step this handler guards.
279
+ * @see err
280
+ */
281
+ err(field) {
282
+ if (!this.errorSource) {
283
+ throw new TypeError('err(field) with no step name is only available inside an error handler — the sub-builder '
284
+ + '.onError((h) => …) / .stepToList(\'error\', (h) => …) passes. Elsewhere, name the step: '
285
+ + "err('<step>', 'message').");
286
+ }
287
+ return errRef(this.errorSource, field);
288
+ }
289
+ /**
290
+ * Add an action node (see `http` / `script` / `subflow`).
291
+ *
292
+ * @param name - The step's id. Read its result downstream with `out('<name>')`,
293
+ * so it must be unique within the flow.
294
+ * @param spec - What the node does, from an action factory.
295
+ * @param options - Node options; `version` selects the exact definition
296
+ * version (see {@link NodeOptions}).
297
+ * @returns This builder, so calls chain.
298
+ */
299
+ step(name, spec, options) {
300
+ // A custom FlowAction subclass carries no `kind` and is lowered through its
301
+ // own contribution; the built-in kind-switched paths never see it, so the
302
+ // stored Step keeps the structural spec type.
303
+ this.steps.push({ kind: 'action', name, spec: spec, ...(options ? { options } : {}) });
304
+ return this;
305
+ }
306
+ /**
307
+ * Handle the PREVIOUS step's failure: if it fails, the flow runs `bodyFn`'s
308
+ * steps instead of continuing.
309
+ *
310
+ * @remarks
311
+ * ```ts
312
+ * .step('fetch', http({ url: apiUrl, managed: true, returns: { items: 'array' } }))
313
+ * .onError((h) => h.return({ result: lit('Article not found') }))
314
+ * .step('total', transform({ … })) // runs only when `fetch` SUCCEEDED
315
+ * .return({ result: out('total') })
316
+ * ```
317
+ *
318
+ * The handler is its own path — anything chained after `.onError(...)` belongs
319
+ * to the SUCCESS path only. Inside it, read the failure with
320
+ * {@link StepList.err} — `h.err('message')` — or with the free
321
+ * `err('<step>', 'message')` if you prefer to name the step. The envelope
322
+ * carries `code`, `message`, `detail`, `category` and `status` (`code` is a
323
+ * string even for an HTTP status, `status` a number), plus an undeclared
324
+ * `response` and `element` the runtime adds.
325
+ *
326
+ * Do NOT reach for `out('<step>', …)` in here. That reads the step's SUCCESS
327
+ * output, which was never written — the handler runs precisely because the
328
+ * step failed — and `check` refuses it (ERROR_ENVELOPE_VIA_OUTPUT). This doc
329
+ * line used to recommend `out('<step>', 'error', 'message')`, which is not
330
+ * even a legal call: `out(step, path?)` takes two arguments.
331
+ *
332
+ * **Three ways the handler can end**: `.return(...)` answers the flow,
333
+ * `.terminate(...)` stops the run, and `h.rejoin('<step>')` compensates and
334
+ * hands control back to a step the success path also reaches — the shape most
335
+ * deployed flows use. A handler that just runs out of steps still gets its own
336
+ * End: rejoining is something the author SAYS, never something wiring infers.
337
+ * See {@link StepList.stepToRef}.
338
+ *
339
+ * Two things this does for you, by construction: it sets
340
+ * `errorHandlingEnabled` on the node in the same write as the edge (without
341
+ * it the platform routes but Studio Web HIDES the handle, so no human can see
342
+ * your handler), and it refuses at build time on a step whose node type has no
343
+ * error port.
344
+ *
345
+ * **A `.loop()` container takes one too.** `core.logic.loop` declares the
346
+ * handle, and a body step's failure routes to it: the container's envelope
347
+ * carries the body's message in `detail` and the failing body step's id in
348
+ * `element`, and the instance completes rather than faulting. `.doWhile()` does
349
+ * NOT — it declares an error variable and no error handle — so that one is
350
+ * refused by name, and its failures belong to the body step that can fail.
351
+ *
352
+ * **A plain `http()` step is the trap worth knowing**: a 404 there is a
353
+ * SUCCESS with `statusCode: 404`, so the handler never runs — use
354
+ * `http({ …, managed: true })` when failures must route. `check` says so
355
+ * (HTTP_ONERROR_V1).
356
+ *
357
+ * Which node types can fail, what the envelope carries, and what a local run
358
+ * does and does not prove: `references/error-handling.md`.
359
+ *
360
+ * @param bodyFn - Receives a sub-builder for the handler's own path. Read the
361
+ * failure inside it with `h.err('message')` ({@link StepList.err}).
362
+ * @returns This builder, positioned on the SUCCESS path — anything chained after
363
+ * this call runs only when the step succeeded.
364
+ * @enforcedBy HTTP_ONERROR_V1 On an http step this needs `managed: true`; on the
365
+ * standalone node a 4xx arrives on the SUCCESS path and no handler runs.
366
+ */
367
+ onError(bodyFn) {
368
+ return this.addPortList('error', bodyFn, '.onError()', 'handler');
369
+ }
370
+ /**
371
+ * The step a port-scoped edge leaves, and the guard that it is allowed to.
372
+ *
373
+ * Scans BACK past edges already attached to the same step: after
374
+ * `.onError(...)` the last entry in the list is that handler, not the action it
375
+ * guards, so `steps.at(-1)` would refuse a second port-edge on the same step —
376
+ * and would attach `.stepToRef('error', …)` to the wrong thing.
377
+ *
378
+ * A port takes exactly one outgoing edge (the definitions say so:
379
+ * `maxConnections: 1`), so a second edge from the same port is refused here
380
+ * rather than emitted and silently dropped downstream.
381
+ */
382
+ portSource(port, called) {
383
+ let i = this.steps.length - 1;
384
+ const attached = [];
385
+ while (i >= 0 && (this.steps[i].kind === 'stepToList' || this.steps[i].kind === 'stepToRef')) {
386
+ attached.push(this.steps[i]);
387
+ i--;
388
+ }
389
+ const source = this.steps[i];
390
+ // A `.loop()` CONTAINER is a legal source for the error port. Its definition
391
+ // declares the handle at both bundled versions (`core.logic.loop@1.0.0` and
392
+ // `@2.4`, gated on `errorHandlingEnabled` like every other error handle), and
393
+ // the platform routes it: a body step throwing inside a measured run left the
394
+ // instance Completed with the container's envelope published — `detail` the
395
+ // body's own message, `element` the id of the body step that failed.
396
+ //
397
+ // `.doWhile()` is NOT: `core.logic.dowhile@1.0` declares an error VARIABLE
398
+ // and no error HANDLE, so an edge would leave a handle the node does not
399
+ // have — the same shape as the agent `memory` handle that fails validate.
400
+ const containerPort = port === 'error' && source?.kind === 'loop';
401
+ if (!source || (source.kind !== 'action' && !containerPort)) {
402
+ throw new TypeError(`${called} leaves a port of the step BEFORE it, and ${source ? `the previous step is a ${source.kind}` : 'there is no previous step'}. `
403
+ + `Write .step('fetch', http({…}))${called}`
404
+ + (source?.kind === 'doWhile' && port === 'error'
405
+ ? ' — a .doWhile() declares an error variable but no error HANDLE (core.logic.dowhile@1.0), so the edge '
406
+ + 'would leave a handle the node does not have. Handle the failure inside the body instead.'
407
+ : ' — a branch, parallel, terminate or return has no port to leave from; a .loop() has one only for '
408
+ + "'error'."));
409
+ }
410
+ const taken = attached.some((s) => (s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === port);
411
+ if (taken) {
412
+ throw new TypeError(`${called} is already set on step "${source.name}"'s "${port}" port. A port takes exactly ONE outgoing edge `
413
+ + `(the definition says so: maxConnections: 1), so put everything that path needs in one place.`);
414
+ }
415
+ return source;
416
+ }
417
+ /**
418
+ * Run a step list from a named PORT of the step this follows — the general form
419
+ * of an error handler, and the reason `.onError()` needs no machinery of its own.
420
+ *
421
+ * @remarks
422
+ * The body is its own path. It ends by answering (`.return`), stopping the run
423
+ * (`.terminate`), or handing control back with `.stepToRef(...)`; a body that
424
+ * simply runs out of steps gets its own End, exactly as an error handler does.
425
+ *
426
+ * The main path is untouched — an edge out of a NON-default port is a side exit,
427
+ * so whatever you chain after this call still belongs to the step's normal
428
+ * continuation.
429
+ *
430
+ * ```ts
431
+ * .step('fetch', http({ … , managed: true }))
432
+ * .stepToList('error', (h) => h.step('log', script({ … })).stepToRef('settle'))
433
+ * .step('parse', script({ … })) // ← still the SUCCESS path
434
+ * ```
435
+ *
436
+ * @param port - The port of the preceding step to leave from, e.g. `'error'`.
437
+ * @param bodyFn - Receives a sub-builder for the path that port leads to.
438
+ * @returns This builder, so calls chain.
439
+ * @see stepToRef
440
+ */
441
+ stepToList(port, bodyFn) {
442
+ return this.addPortList(port, bodyFn, '.stepToList()', 'body');
443
+ }
444
+ /**
445
+ * The shared body of `.stepToList()` and `.onError()`.
446
+ *
447
+ * Private because `called` and `noun` exist only so a refusal names the method
448
+ * the author actually called — they are not part of the surface, and a public
449
+ * parameter nobody should pass is a worse trade than one extra hop.
450
+ */
451
+ addPortList(port, bodyFn, called, noun) {
452
+ const prev = this.portSource(port, called);
453
+ const h = new StepList();
454
+ // The handler knows which step failed, so `h.err(field)` does not have to be
455
+ // told. Set for the error port only: on any other port list the preceding
456
+ // step SUCCEEDED, and there is no envelope to read.
457
+ if (port === 'error')
458
+ h.errorSource = prev.name;
459
+ bodyFn?.(h);
460
+ if (h.steps.length === 0) {
461
+ throw new TypeError(`${called} on step "${prev.name}" has an empty ${noun}. An edge to nothing is worse than none — `
462
+ + `the path still leaves the main flow. Return a value, terminate the run, or do the compensating work.`);
463
+ }
464
+ this.steps.push({ kind: 'stepToList', port, body: h.steps });
465
+ return this;
466
+ }
467
+ /**
468
+ * Branch on a condition. `thenFn`/`elseFn` receive a sub-builder for each arm.
469
+ *
470
+ * @remarks
471
+ * Each arm can name itself with `.label(...)` — the then-arm's label becomes the
472
+ * node's `trueLabel`, the else-arm's its `falseLabel` (the text the designer
473
+ * shows on the two outgoing edges). Omit it and the arms keep the default
474
+ * `"True"` / `"False"`.
475
+ *
476
+ * @param name - The decision node's id.
477
+ * @param cond - The condition, as an expression — typically `js\`…\``.
478
+ * @param thenFn - Receives a sub-builder for the TRUE arm.
479
+ * @param elseFn - Receives a sub-builder for the FALSE arm. Omit it for a
480
+ * decision whose false path simply continues.
481
+ * @param options - Node options; `version` selects the exact definition
482
+ * version (see {@link NodeOptions}).
483
+ * @returns This builder, after both arms have rejoined.
484
+ */
485
+ branch(name, cond, thenFn, elseFn, options) {
486
+ if (options?.updates) {
487
+ throw new TypeError('.branch(): { updates } is not supported here — the platform strips variable updates from '
488
+ + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
489
+ }
490
+ const t = new ArmBuilder();
491
+ thenFn(t);
492
+ const e = new ArmBuilder();
493
+ if (elseFn)
494
+ elseFn(e);
495
+ // Normalize the condition the way `.switch` normalizes its discriminant: an
496
+ // authored `.flow.ts` runs with TYPES ERASED, so a bare value reaches here
497
+ // as itself rather than an Expr, and every later reader (check's reference
498
+ // scan, the serializer) then walks `undefined`. Wrapping it makes the
499
+ // mistake a named rung-1 diagnostic (BRANCH_CONST_COND) instead of
500
+ // `Cannot read properties of undefined (reading 'matchAll')`.
501
+ this.steps.push({
502
+ kind: 'branch',
503
+ name,
504
+ ...(t.armLabel !== undefined ? { trueLabel: t.armLabel } : {}),
505
+ ...(e.armLabel !== undefined ? { falseLabel: e.armLabel } : {}),
506
+ cond: toExpr(cond),
507
+ then: t.steps,
508
+ otherwise: e.steps,
509
+ ...(options ? { options } : {}),
510
+ });
511
+ return this;
512
+ }
513
+ /**
514
+ * N-way branch on the value of `on` — one arm per case, plus an optional
515
+ * default arm. Each arm gets its own sub-builder, exactly like `.branch`.
516
+ *
517
+ * @remarks
518
+ * A case matches when `on === case.value`, compared the way JavaScript's own
519
+ * `switch` compares: **strictly, so the type counts**. `value: 2` matches a
520
+ * `types.number` input carrying `2`; `value: '2'` does not. Cases are
521
+ * evaluated top to bottom and the first match wins.
522
+ *
523
+ * @example
524
+ * **Route on a value, with a default arm**
525
+ * ```ts
526
+ * .switch('routeQuarter', input('quarter'), [
527
+ * { value: 1, body: (b) => b.step('spring', script({ code: 'return "Spring";' })) },
528
+ * { value: 2, body: (b) => b.step('summer', script({ code: 'return "Summer";' })) },
529
+ * ], (other) => other.step('unknown', script({ code: 'return "Unknown";' })))
530
+ * ```
531
+ *
532
+ * @param name - The switch node's id.
533
+ * @param on - The value to compare each case against.
534
+ * @param cases - One entry per arm, evaluated top to bottom; the first strict
535
+ * match wins. `label` names the arm's outgoing edge.
536
+ * @param defaultFn - Receives a sub-builder for the arm taken when nothing
537
+ * matches. Omit it and an unmatched value simply continues.
538
+ * @param options - Node options; `version` selects the exact definition
539
+ * version (see {@link NodeOptions}).
540
+ * @returns This builder, after every arm has rejoined.
541
+ */
542
+ switch(name, on, cases, defaultFn, options) {
543
+ if (options?.updates) {
544
+ throw new TypeError('.switch(): { updates } is not supported here — the platform strips variable updates from '
545
+ + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
546
+ }
547
+ const arms = (cases ?? []).map((c) => {
548
+ const arm = new ArmBuilder();
549
+ c.body?.(arm);
550
+ // An arm's own `.label(...)` wins over the case config's `label`; either
551
+ // overrides the serializer's derived `Case "<value>"`.
552
+ const label = arm.armLabel ?? c.label;
553
+ return { value: c.value, ...(label !== undefined ? { label } : {}), body: arm.steps };
554
+ });
555
+ let dflt;
556
+ if (defaultFn) {
557
+ const d = new StepList();
558
+ defaultFn(d);
559
+ dflt = d.steps;
560
+ }
561
+ // `on` is coerced rather than trusted: the authored `.flow.ts` runs with
562
+ // types stripped, so a plain string here (`.switch('q', 'quarter', …)`)
563
+ // would otherwise reach the serializer as `undefined === 2`. As a literal
564
+ // Expr it becomes a check-time diagnostic instead (SWITCH_CONST_ON).
565
+ this.steps.push({
566
+ kind: 'switch',
567
+ name,
568
+ on: toExpr(on),
569
+ cases: arms,
570
+ ...(dflt !== undefined ? { default: dflt } : {}),
571
+ ...(options ? { options } : {}),
572
+ });
573
+ return this;
574
+ }
575
+ /**
576
+ * Run two or more arms **in parallel** from this point and join them back
577
+ * together on a Merge node — the one place the builder stops being a straight
578
+ * chain.
579
+ *
580
+ * @remarks
581
+ * `name` names the **Merge** node (the join). Each arm is a sub-builder, just
582
+ * like a `.branch` arm, and every arm starts from the same place the chain had
583
+ * reached, so the graph forks there and reconverges on the Merge.
584
+ *
585
+ * Anything you chain afterwards continues from the Merge, so it can read every
586
+ * arm's output. The Merge itself computes nothing and publishes nothing — read
587
+ * the steps inside the arms, never `$vars.<merge>`.
588
+ *
589
+ * Arm semantics, what a local run does and does not prove, and the rules
590
+ * `check` enforces: `references/parallel-merge.md`.
591
+ *
592
+ * @example
593
+ * **Fetch two things at once, then combine them after the join**
594
+ * ```ts
595
+ * .parallel('sync', [
596
+ * (a) => a.step('fetchWeather', script({ code: 'return "sunny";' })),
597
+ * (b) => b.step('fetchNews', script({ code: 'return "quiet";' })),
598
+ * ])
599
+ * .step('combine', script({ code: 'return $vars.fetchWeather.output + $vars.fetchNews.output;' }))
600
+ * ```
601
+ *
602
+ * @param name - The split node's id. The Merge node is derived from it.
603
+ * @param arms - One callback per parallel arm, each receiving its own
604
+ * sub-builder. Two or more.
605
+ * @param options - Node options; `version` selects the exact definition
606
+ * version (see {@link NodeOptions}).
607
+ * @returns This builder, positioned after the Merge — so what you chain next
608
+ * runs once, when every arm has finished.
609
+ */
610
+ parallel(name, arms, options) {
611
+ if (options?.updates) {
612
+ throw new TypeError('.parallel(): { updates } is not supported here — the platform strips variable updates from '
613
+ + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
614
+ }
615
+ const built = (arms ?? []).map((armFn) => {
616
+ const a = new StepList();
617
+ armFn?.(a);
618
+ return a.steps;
619
+ });
620
+ this.steps.push({ kind: 'parallel', name, arms: built, ...(options ? { options } : {}) });
621
+ return this;
622
+ }
623
+ /**
624
+ * Iterate `collection`; `bodyFn` receives a sub-builder for the loop body.
625
+ *
626
+ * @param name - The loop node's id.
627
+ * @param collection - The array to iterate, as a reference — `v('items')` or
628
+ * `out('fetch', 'body.items')`.
629
+ * @param bodyFn - Receives a sub-builder for the body, run once per element.
630
+ * @param options - Node options; `version` selects the exact definition
631
+ * version (see {@link NodeOptions}).
632
+ * @returns This builder, positioned after the loop.
633
+ */
634
+ loop(name, collection, bodyFn, options) {
635
+ const b = new StepList();
636
+ bodyFn(b);
637
+ this.steps.push({ kind: 'loop', name, collection: toExpr(collection), body: b.steps, ...(options ? { options } : {}) });
638
+ return this;
639
+ }
640
+ /**
641
+ * Run the body, then repeat **while `condition` is true** — the condition is
642
+ * checked AFTER each iteration, so the body always runs at least once
643
+ * (`core.logic.dowhile`). The container publishes no data output; write
644
+ * results to a `.var()` from inside the body (`{ updates }`), and read the
645
+ * loop's progress nowhere — unlike `.loop()` there is no `currentItem`.
646
+ *
647
+ * @example
648
+ * **Paginate until the API says stop**
649
+ * ```ts
650
+ * .var('page', types.number, 1)
651
+ * .doWhile('paginate', js`$vars.fetch.output.hasNextPage === true`, (b) => b
652
+ * .step('fetch', http({ url: tmpl`https://api.example.test/items?page=${v('page')}`, method: 'GET', managed: false, returns: { hasNextPage: 'boolean' } }), {
653
+ * updates: { page: js`$vars.page + 1` },
654
+ * }), { limit: 50 })
655
+ * ```
656
+ *
657
+ * @param name - The container node's id.
658
+ * @param condition - Repeat while this is true, checked after each pass.
659
+ * @param bodyFn - Receives a sub-builder for the body, run once per pass.
660
+ * @param options - `limit` (1–10,000; blank = platform default 10,000),
661
+ * `breakEnabled`, and the common node options (see {@link DoWhileOptions}).
662
+ * @returns This builder, positioned after the loop.
663
+ */
664
+ doWhile(name, condition, bodyFn, options) {
665
+ const b = new StepList();
666
+ bodyFn(b);
667
+ this.steps.push({ kind: 'doWhile', name, condition: toExpr(condition), body: b.steps, ...(options ? { options } : {}) });
668
+ return this;
669
+ }
670
+ /**
671
+ * Exit the enclosing `.loop()` / `.doWhile()` through its **break handle**,
672
+ * ending the whole loop now — not just this iteration. Terminal on its path:
673
+ * nothing may follow it, exactly like `.terminate()`. Using it enables the
674
+ * container's break handle automatically. Compilation fails when there is no
675
+ * enclosing container.
676
+ *
677
+ * @returns This builder. Nothing after this call on the same path can run.
678
+ */
679
+ break() {
680
+ this.steps.push({ kind: 'break' });
681
+ return this;
682
+ }
683
+ /**
684
+ * Stop the **whole run**, here and now — not just this path.
685
+ *
686
+ * @remarks
687
+ * That is the only difference from `.return()`, and it is a big one: inside a
688
+ * `.parallel` arm a terminate **aborts the sibling arms**, whatever they were
689
+ * in the middle of. A `.return()` in the same place would only end its own
690
+ * path and leave the others running.
691
+ *
692
+ * Nothing may follow a terminate: it publishes no value, and no step after it
693
+ * — in its arm or chained behind it — can ever run.
694
+ *
695
+ * What aborts, what the run reports, and what each local rung can prove:
696
+ * `references/terminate.md`.
697
+ *
698
+ * @example
699
+ * **Abort the sibling arm from inside a parallel**
700
+ * ```ts
701
+ * .parallel('branches', [
702
+ * (a) => a.terminate('stopEverything'),
703
+ * (b) => b.step('wait', delay({ duration: 'PT10S' })).return({ message: 'done' }),
704
+ * ])
705
+ * ```
706
+ *
707
+ * @param name - The terminate node's id.
708
+ * @param label - Text the designer shows on the node. Omit it for the default.
709
+ * @param options - Node options; `version` selects the exact definition
710
+ * version (see {@link NodeOptions}).
711
+ * @returns This builder. Nothing after this call on the same path can run.
712
+ */
713
+ terminate(name, label, options) {
714
+ this.steps.push({ kind: 'terminate', name, ...(label !== undefined ? { label } : {}), ...(options ? { options } : {}) });
715
+ return this;
716
+ }
717
+ stepToRef(portOrTarget, maybeTarget) {
718
+ const port = maybeTarget === undefined ? 'output' : portOrTarget;
719
+ const target = maybeTarget === undefined ? portOrTarget : maybeTarget;
720
+ if (typeof target !== 'string' || target.length === 0) {
721
+ throw new TypeError(`.stepToRef() takes the NAME of the step to continue at — .stepToRef('priceIt') — and got ${JSON.stringify(target)}. `
722
+ + `It is the same name you passed to .step(), so the edge can be wired after the whole flow is built.`);
723
+ }
724
+ // A non-default port leaves the PRECEDING step, so it is guarded the same way
725
+ // `.stepToList()` is; the default port continues the list and needs no source.
726
+ if (port !== 'output')
727
+ this.portSource(port, `.stepToRef('${port}', …)`);
728
+ this.steps.push({ kind: 'stepToRef', port, target });
729
+ return this;
730
+ }
731
+ /**
732
+ * Terminate this path, binding flow outputs to expressions.
733
+ *
734
+ * @param values - One entry per declared flow output. A raw value is wrapped as
735
+ * a literal; an {@link Expr} is emitted as a reference.
736
+ * @param options - Node options; `version` selects the exact definition
737
+ * version (see {@link NodeOptions}).
738
+ * @returns This builder. This path has answered, so nothing after it runs.
739
+ */
740
+ return(values = {}, options) {
741
+ const norm = {};
742
+ for (const [k, val] of Object.entries(values))
743
+ norm[k] = toExpr(val);
744
+ this.steps.push({ kind: 'return', name: 'return', values: norm, ...(options ? { options } : {}) });
745
+ return this;
746
+ }
747
+ }
748
+ /**
749
+ * The sub-builder an ARM callback receives — a `StepList` that can also name
750
+ * itself with `.label(...)`.
751
+ *
752
+ * @remarks
753
+ * It exists so `.label()` is available exactly where it means something (a
754
+ * `.branch` or `.switch` arm) and NOT on the top-level flow, which names itself
755
+ * with `.name(...)`. The parent's `branch`/`switch` reads `armLabel` back after
756
+ * running the callback and lowers it to the node's arm label (`trueLabel` /
757
+ * `falseLabel` for a decision, the case `label` for a switch).
758
+ */
759
+ class ArmBuilder extends StepList {
760
+ armLabel;
761
+ /**
762
+ * Name this arm. The label is what the designer shows on the arm's outgoing
763
+ * edge; it also survives a round-trip through Flow JSON, where a bare
764
+ * `"True"` / `"False"` / `Case "<value>"` would not carry the author's intent.
765
+ * Order-independent — call it anywhere in the arm's chain.
766
+ *
767
+ * @param text - The label the designer shows on this arm's outgoing edge.
768
+ * @returns This builder, so calls chain.
769
+ */
770
+ label(text) {
771
+ this.armLabel = text;
772
+ return this;
773
+ }
774
+ }
775
+ class FlowBuilder extends StepList {
776
+ _id;
777
+ _name;
778
+ _description;
779
+ _layout;
780
+ _stickyNotes;
781
+ _version = '1.0.0';
782
+ _schemaVersion;
783
+ _inputs = [];
784
+ _outputs = [];
785
+ _vars = [];
786
+ _trigger;
787
+ _triggerOptions;
788
+ _entryPoints = [];
789
+ _triggerId;
790
+ constructor(_id) {
791
+ super();
792
+ this._id = _id;
793
+ this._name = _id;
794
+ }
795
+ /**
796
+ * Set the flow's display name.
797
+ *
798
+ * @param n - The name the designer shows. Defaults to the flow's id.
799
+ * @returns This builder, so calls chain.
800
+ * @defaultValue the flow's id
801
+ */
802
+ name(n) {
803
+ this._name = n;
804
+ return this;
805
+ }
806
+ /**
807
+ * Set the flow's description — persisted as the top-level `description`
808
+ * field of the emitted `.flow`, shown in the designer's flow header.
809
+ *
810
+ * @param text - What this flow does, for humans.
811
+ * @returns This builder, so calls chain.
812
+ */
813
+ description(text) {
814
+ this._description = text;
815
+ return this;
816
+ }
817
+ /**
818
+ * Set designer layout — node positions/sizes and edge routes — keyed by the
819
+ * flow's own logical ids (step names, the trigger id, `end`). Optional pure
820
+ * metadata: nothing here changes what the flow does, and omitting it leaves
821
+ * the serializer's default arrangement. Unknown keys fail compilation rather
822
+ * than being dropped.
823
+ *
824
+ * @param layout - Node layout by step name, edge routes by `"from->to"`.
825
+ * @returns This builder, so calls chain.
826
+ */
827
+ layout(layout) {
828
+ this._layout = layout;
829
+ return this;
830
+ }
831
+ /**
832
+ * Pin free-text notes to the canvas, for whoever reads the diagram.
833
+ *
834
+ * @remarks
835
+ * Sticky notes are the only annotation `.flow` has. Each becomes a real
836
+ * `stickyNote` node, which the platform skips during validation and
837
+ * conversion and whose edges it drops — so a note can never change what the
838
+ * flow does, and `check` treats it as inert.
839
+ *
840
+ * Calling this twice REPLACES the set rather than appending, matching
841
+ * `.layout()`.
842
+ *
843
+ * @example
844
+ * ```ts
845
+ * .stickyNotes([
846
+ * { content: 'Rates refresh at 02:00 UTC', x: 240, y: 40 },
847
+ * { id: 'why-retry', content: 'The vendor 502s under load.', color: 'pink',
848
+ * x: 240, y: 320, width: 220, height: 120 },
849
+ * ])
850
+ * ```
851
+ *
852
+ * @param notes - The notes to pin. Position is required; `id`, `color` and
853
+ * size have defaults.
854
+ * @returns This builder, so calls chain.
855
+ */
856
+ stickyNotes(notes) {
857
+ this._stickyNotes = notes;
858
+ return this;
859
+ }
860
+ /**
861
+ * Set what starts the flow. Omit this call for the **manual** trigger (a
862
+ * caller starts the flow on demand) — that is the default and what most
863
+ * flows want.
864
+ *
865
+ * @example
866
+ * **Run the flow hourly instead of on demand**
867
+ * ```ts
868
+ * .trigger(scheduled({ every: 'R/PT1H' }))
869
+ * ```
870
+ *
871
+ * @param spec - What starts the flow, from `scheduled(...)` or `onEvent(...)`.
872
+ * @param options - Node options; `version` selects the exact definition
873
+ * version (see {@link NodeOptions}).
874
+ * @returns This builder, so calls chain.
875
+ * @defaultValue the manual trigger
876
+ */
877
+ trigger(spec, options) {
878
+ if (options?.updates) {
879
+ throw new TypeError('.trigger(): { updates } is not supported — a trigger has not run anything to assign from.');
880
+ }
881
+ this._trigger = spec;
882
+ if (options)
883
+ this._triggerOptions = options;
884
+ return this;
885
+ }
886
+ /**
887
+ * Add an ADDITIONAL flow root: its own trigger node, its own
888
+ * scoped inputs (read them with `entryInput('<id>', '<name>')`), and an
889
+ * optional prefix that runs before the root joins the shared body. Without a
890
+ * prefix the root connects straight to the first shared step; a prefix that
891
+ * ends terminally (or hands off with `.stepToRef()`) joins nothing.
892
+ *
893
+ * @remarks
894
+ * The DEFAULT root stays `.trigger()` / `.input()` — one flow, one default
895
+ * entry point, marked `isDefaultEntryPoint` in the emitted file. Every root
896
+ * (default included) gets its platform entry-point identity when a flow has
897
+ * more than one.
898
+ *
899
+ * @example
900
+ * **A manual flow with a second, nightly-scheduled root**
901
+ * ```ts
902
+ * flow('order-intake')
903
+ * .input({ order: types.object }) // the default (manual) root
904
+ * .entryPoint('nightly', scheduled({ every: 'R/P1D' }), {
905
+ * inputs: { batchDate: types.string },
906
+ * }, (b) => b.step('loadBatch', script({ code: 'return { order: { id: $vars.nightly.output.batchDate } };', returns: 'object' })))
907
+ * .step('normalize', script({ code: 'return 1;' })) // shared body
908
+ * ```
909
+ *
910
+ * @param id - The root's trigger-node id — unique among entry points, steps,
911
+ * and the default trigger id. Expressions read it as `$vars.<id>.output.*`.
912
+ * @param trigger - What fires this root: `manual()`, `scheduled(...)`, or
913
+ * `onEvent(...)`.
914
+ * @param options - `version` (exact trigger definition version) and `inputs`
915
+ * — this root's scoped inputs, declared like `.input()`.
916
+ * @param prefixFn - Optional sub-builder for the root's prefix steps.
917
+ * @returns This builder, so calls chain.
918
+ */
919
+ entryPoint(id, trigger, options, prefixFn) {
920
+ const inputs = [];
921
+ for (const [name, value] of Object.entries(options?.inputs ?? {})) {
922
+ inputs.push({ name, ...varDeclOf('input', name, value) });
923
+ }
924
+ const b = new StepList();
925
+ prefixFn?.(b);
926
+ this._entryPoints.push({
927
+ id,
928
+ trigger: trigger,
929
+ ...(options?.version !== undefined ? { options: { version: options.version } } : {}),
930
+ inputs,
931
+ steps: b.steps,
932
+ });
933
+ return this;
934
+ }
935
+ /**
936
+ * Rename the trigger node. The designer can rename it, so the SDK can too.
937
+ *
938
+ * @remarks
939
+ * The id is not cosmetic: a flow's declared inputs are published as the trigger
940
+ * node's output, so `input('amount')` reads `$vars.<triggerId>.output.amount`.
941
+ * Renaming the node rewrites every such reference with it.
942
+ *
943
+ * @example
944
+ * **Rename the trigger; input reads follow**
945
+ * ```ts
946
+ * .triggerId('intake') // input('amount') → $vars.intake.output.amount
947
+ * ```
948
+ *
949
+ * @param id - The trigger node's new id. Every `input(...)` reference is
950
+ * rewritten to read through it.
951
+ * @returns This builder, so calls chain.
952
+ * @defaultValue `'start'`
953
+ */
954
+ triggerId(id) {
955
+ this._triggerId = id;
956
+ return this;
957
+ }
958
+ /**
959
+ * Set the flow's version.
960
+ *
961
+ * @param vsn - The version string. It does NOT reach the emitted `.flow`.
962
+ * @returns This builder, so calls chain.
963
+ * @deprecated Top-level `version` in a `.flow` file is the FILE-FORMAT
964
+ * version, owned by the serializer's format profile (design §5.4) — it was
965
+ * never an author-controlled artifact version, and the serializer has always
966
+ * overwritten this value. The call is kept for source compatibility and
967
+ * ignored. To target a specific file-format version, use
968
+ * {@link FlowBuilder.schemaVersion}; there is no author-controlled artifact
969
+ * version in the format for this to have meant.
970
+ */
971
+ version(vsn) {
972
+ this._version = vsn;
973
+ return this;
974
+ }
975
+ /**
976
+ * Declare the `.flow` FILE-FORMAT (schema) version this flow targets.
977
+ *
978
+ * @remarks
979
+ * This is the document's grammar version — the top-level `version` a `.flow`
980
+ * carries — and it is the one version concept that IS the author's to set,
981
+ * which is exactly what {@link FlowBuilder.version} is not. Omit it and the
982
+ * flow declares the SDK's default (the fleet write floor, `1.9`), which is
983
+ * what every flow authored before this method existed already did.
984
+ *
985
+ * Its real job is BROWNFIELD PRESERVATION. `flow-decompile` emits this call
986
+ * with the version it read, so a decompile → edit → compile round-trip
987
+ * re-declares the file's own version instead of silently rewriting a `1.10`
988
+ * file as `1.9`. Migrating a document BETWEEN versions stays
989
+ * `uip maestro flow migrate`'s job: a hop can carry data repairs (the
990
+ * 1.9 → 1.10 hop repairs connector binding names) that this SDK has no way to
991
+ * reproduce from the source alone.
992
+ *
993
+ * Only versions this build can actually write are accepted — `1.6` through
994
+ * `1.10`, the band whose schemas are the same shape as what the serializer
995
+ * emits. A version is a CLAIM that the whole document matches that schema,
996
+ * not a switch that reshapes it, so declaring one the serializer cannot emit
997
+ * would write a false claim; the call throws instead. Both directions off the
998
+ * floor cost something — above it, a reader pinned lower (an older CLI, an
999
+ * Automation Suite ring) refuses the file; below it, every reader migrates
1000
+ * forward on open — so `check()` reports `SCHEMA_VERSION_ABOVE_FLOOR` /
1001
+ * `SCHEMA_VERSION_BELOW_FLOOR` rather than deciding for you.
1002
+ *
1003
+ * @example
1004
+ * **Round-trip a 1.10 flow without downgrading it**
1005
+ * ```ts
1006
+ * flow('claims').schemaVersion('1.10')
1007
+ * ```
1008
+ *
1009
+ * @param version - A writable file-format version, e.g. `'1.9'` or `'1.10'`.
1010
+ * @returns This builder, so calls chain.
1011
+ * @throws Error when this SDK build cannot write `version`.
1012
+ * @defaultValue `'1.9'` — `FLOW_FORMAT_PROFILE.version`, the fleet write floor.
1013
+ */
1014
+ schemaVersion(version) {
1015
+ const refusal = schemaVersionRefusal(FLOW_SCHEMA_VERSIONS, version);
1016
+ if (refusal)
1017
+ throw new Error(refusal);
1018
+ this._schemaVersion = version;
1019
+ return this;
1020
+ }
1021
+ /**
1022
+ * Declare the flow's inputs. Read them with `input('<name>')`.
1023
+ *
1024
+ * @param shape - Input names to type descriptors, e.g. `{ amount: types.number }`.
1025
+ * @returns This builder, so calls chain.
1026
+ * @see input
1027
+ */
1028
+ input(shape) {
1029
+ for (const [name, value] of Object.entries(assertShape('input', shape))) {
1030
+ this._inputs.push({ name, ...varDeclOf('input', name, value) });
1031
+ }
1032
+ return this;
1033
+ }
1034
+ /**
1035
+ * Declare the flow's outputs — what `.return(...)` binds values to.
1036
+ *
1037
+ * @param shape - Output names to type descriptors, e.g. `{ total: types.number }`.
1038
+ * @returns This builder, so calls chain.
1039
+ */
1040
+ output(shape) {
1041
+ for (const [name, value] of Object.entries(assertShape('output', shape))) {
1042
+ this._outputs.push({ name, ...varDeclOf('output', name, value) });
1043
+ }
1044
+ return this;
1045
+ }
1046
+ /**
1047
+ * Declare a flow-level variable. Read it with `v('<name>')`.
1048
+ *
1049
+ * @param name - The variable's name.
1050
+ * @param type - One of the `types.*` descriptors.
1051
+ * @param defaultValue - Its initial value. Omit it to start undeclared.
1052
+ * @returns This builder, so calls chain.
1053
+ * @see v
1054
+ */
1055
+ var(name, type, defaultValue) {
1056
+ const decl = varDeclOf('var', name, type);
1057
+ if (defaultValue !== undefined)
1058
+ decl.default = defaultValue;
1059
+ this._vars.push({ name, ...decl });
1060
+ return this;
1061
+ }
1062
+ /**
1063
+ * Finish the flow and return the graph the compiler serializes.
1064
+ *
1065
+ * @returns The built description — inputs, outputs, variables, trigger and steps.
1066
+ * @throws FlowBuildError when an expression references something undeclared.
1067
+ */
1068
+ build() {
1069
+ const built = {
1070
+ id: this._id,
1071
+ name: this._name,
1072
+ ...(this._description !== undefined ? { description: this._description } : {}),
1073
+ ...(this._layout !== undefined ? { layout: this._layout } : {}),
1074
+ ...(this._stickyNotes !== undefined ? { stickyNotes: this._stickyNotes } : {}),
1075
+ version: this._version,
1076
+ ...(this._schemaVersion !== undefined ? { schemaVersion: this._schemaVersion } : {}),
1077
+ inputs: this._inputs,
1078
+ outputs: this._outputs,
1079
+ vars: this._vars,
1080
+ steps: this.steps,
1081
+ ...(this._trigger !== undefined ? { trigger: this._trigger } : {}),
1082
+ ...(this._triggerOptions !== undefined ? { triggerOptions: this._triggerOptions } : {}),
1083
+ ...(this._entryPoints.length ? { entryPoints: this._entryPoints } : {}),
1084
+ ...(this._triggerId !== undefined ? { triggerId: this._triggerId } : {}),
1085
+ };
1086
+ // First-level validation, intrinsic to running the SDK: catch a definitely-
1087
+ // broken expression HERE, before the model is serialized, rather than three
1088
+ // steps later at `uip maestro flow validate`. Throws so nothing invalid is
1089
+ // written; the deeper checks stay with the validate CLI. See flow-expr-check.
1090
+ const errors = checkFlowExpressions(built).filter((d) => d.level === 'error');
1091
+ if (errors.length)
1092
+ throw new FlowBuildError(built, errors);
1093
+ return built;
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Start building a flow with the given id.
1098
+ *
1099
+ * @param id - The flow's stable identifier, and its display name until
1100
+ * `.name(...)` says otherwise.
1101
+ * @returns A {@link FlowBuilder} to declare inputs and chain steps onto.
1102
+ * @example
1103
+ * ```ts
1104
+ * export default flow('hello').name('Hello')
1105
+ * .output({ greeting: types.string })
1106
+ * .step('greet', script({ code: 'return "hi";', returns: 'string' }))
1107
+ * .return({ greeting: out('greet') })
1108
+ * .build();
1109
+ * ```
1110
+ */
1111
+ export function flow(id) {
1112
+ return new FlowBuilder(id);
1113
+ }
1114
+ /**
1115
+ * Run another flow as one step, and read back what it returns.
1116
+ *
1117
+ * @remarks
1118
+ * The child is an ordinary `flow()` in the same file — its own inputs, steps
1119
+ * and `.return()`. Don't call `.build()` on it; the parent's `.build()` collects
1120
+ * it. Two scopes, joined only by this call: the child sees NOTHING of the
1121
+ * parent's variables except the values passed in here.
1122
+ *
1123
+ * `inputs` keys are the CHILD's declared input names; the values are
1124
+ * parent-scope expressions. A subflow's output is an OBJECT of the child's
1125
+ * declared outputs, so read it with the output's name —
1126
+ * `out('callReverse', 'reversed')`, never a bare `out('callReverse')`.
1127
+ *
1128
+ * Scopes, reuse, nesting, and what a local run does and does not prove:
1129
+ * `references/subflow.md`.
1130
+ *
1131
+ * @example
1132
+ * **Call a child flow and read its named output**
1133
+ * ```ts
1134
+ * const reverse = flow('reverse-text')
1135
+ * .input({ text: types.string })
1136
+ * .output({ reversed: types.string })
1137
+ * .step('rev', script({ code: 'return $vars.text.split("").reverse().join("");' }))
1138
+ * .return({ reversed: out('rev') });
1139
+ *
1140
+ * export default flow('reverse-outer')
1141
+ * .input({ text: types.string })
1142
+ * .output({ result: types.string })
1143
+ * .step('callReverse', subflow(reverse, { text: input('text') }))
1144
+ * .return({ result: out('callReverse', 'reversed') }) // NAME the child output
1145
+ * .build();
1146
+ * ```
1147
+ *
1148
+ * @param child - The child flow — a `flow()` builder or an already-built flow.
1149
+ * @param inputs - One value per the CHILD's declared input names, as parent-scope
1150
+ * expressions.
1151
+ * @returns An action spec for `.step(...)`.
1152
+ */
1153
+ export function subflow(child, inputs = {}) {
1154
+ // Accept the builder OR an already-built flow. Authors reach for the builder
1155
+ // (the parent's `.build()` is the file's one terminal call), and a stray
1156
+ // `.build()` on the child should not be an error worth a diagnostic.
1157
+ const built = typeof child?.build === 'function'
1158
+ ? child.build()
1159
+ : child;
1160
+ const norm = {};
1161
+ for (const [k, val] of Object.entries(inputs ?? {}))
1162
+ norm[k] = toExpr(val);
1163
+ return builtinActionSpec({ kind: 'subflow', child: built, inputs: norm });
1164
+ }