@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,1108 @@
1
+ /**
2
+ * bpmn/decompile — the reverse of `serialize.ts`: turn a real UiPath `.bpmn` back
3
+ * into `.bpmn.ts` builder source whose default export re-serializes to the **same**
4
+ * XML.
5
+ *
6
+ * Phase 4 of `docs/BPMN_COVERAGE_DESIGN.md`. A BPMN process is a flat graph of
7
+ * elements plus edges, and every builder method is 1-1 with an element — so this
8
+ * is a straight structural walk of the moddle tree, with no control-flow inference
9
+ * and no AST parsing. Expressions are opaque strings on both sides, so they emit
10
+ * as string literals; no expression parser is needed.
11
+ *
12
+ * **The round-trip target is byte-identical XML**, not merely structural
13
+ * equivalence — `serialize` is deterministic, so anything less means something was
14
+ * lost. `bpmn-decompile.test.ts` asserts exactly that over the whole corpus, which
15
+ * is what keeps the claims below honest.
16
+ *
17
+ * What does NOT survive, and why:
18
+ *
19
+ * - **`bpmndi` diagram layout.** The SDK emits none and `tidy` regenerates it, so
20
+ * there is nothing to preserve. A decompiled file recompiles to semantic-only
21
+ * XML; re-run `tidy` for a canvas.
22
+ * - **Comments, method order, and formatting.** Regenerated, not preserved.
23
+ *
24
+ * Everything else round-trips: events and their definitions, all four gateways,
25
+ * script/variable tasks, sub-processes (recursively), boundary events, flows and
26
+ * conditions, variables, bindings, retry, loops, skip conditions, and every
27
+ * registry-typed node — connector tasks included.
28
+ *
29
+ * ## Connector tasks, and why they used to be refused
30
+ *
31
+ * `Intsvc.ActivityExecution` was refused on the grounds that it carries three things
32
+ * no builder surface modelled. One of the three was never really a blocker, and the
33
+ * other two were emitter gaps rather than facts about connectors:
34
+ *
35
+ * 1. **The library's ACTION id** — the XML has `objectName="send_message_to_user_v2"`
36
+ * and `operation="Create"`, never the library's `send-message-to-user`. True, and
37
+ * it only matters if the output has to be a `.connector(key, action, …)` call.
38
+ * It does not: the generic `.activity()` with `contextVerbatim` reproduces the
39
+ * context rows exactly as written, and no library is consulted. Refusing over this
40
+ * treated "cannot recover the prettiest source form" as "cannot decompile".
41
+ * 2. **PER-INPUT targets** — `pathParameters` / `queryParameters` / `body` each carry
42
+ * their own `target`. This was a real bug, and not in this file: `separateInputs`
43
+ * applied the spec's single `inputTarget` to every sibling row, collapsing all
44
+ * three onto `target="body"`. `bpmn/connector.ts` never hit it because it writes
45
+ * its own rows with the targets hardcoded.
46
+ * 3. **Output rows carrying a JSON RESPONSE SCHEMA in their body** — a real surface
47
+ * gap. `outputs` is a variable→expression map with nowhere to put a type or a
48
+ * body, so the schema had nowhere to live. `TypedOutputRow` is that surface.
49
+ *
50
+ * With 2 and 3 closed, a connector round-trips byte-identically and needs NO library
51
+ * on the way back, because everything it needs is already in the artifact. Measured
52
+ * on real Slack and Customer.io operations, the second of which exercises the
53
+ * path/query split.
54
+ */
55
+ import BpmnModdle from 'bpmn-moddle';
56
+ import { createRequire } from 'node:module';
57
+ import { registryType } from './typed-node.js';
58
+ import { errorSchema, errorSchemaWithResponse } from './bpmn-sdk.js';
59
+ import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
60
+ import { preserveSchemaVersion } from '../schema-version.js';
61
+ const require = createRequire(import.meta.url);
62
+ const uipathDescriptor = require('./uipath-moddle.v1.json');
63
+ // ─── Source emitters ──────────────────────────────────────────────────────────
64
+ /**
65
+ * A JS/TS string literal.
66
+ *
67
+ * `JSON.stringify` escapes quotes, backslashes and control characters, so a value
68
+ * out of an untrusted `.bpmn` — a label, an expression, a URL — can never break
69
+ * out of the literal and become code in the emitted source. U+2028/U+2029 are
70
+ * escaped too: legal in JSON, historically illegal in JS string literals.
71
+ */
72
+ function str(s) {
73
+ return JSON.stringify(String(s ?? '')).replace(/[\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16)}`);
74
+ }
75
+ /** An arbitrary JSON value as an inert TS literal. Never executable. */
76
+ function literal(v) {
77
+ return JSON.stringify(v ?? null);
78
+ }
79
+ /**
80
+ * Schema bodies that need no declaration, because the platform defines them.
81
+ *
82
+ * `V2EndEventBody.BuildErrorCoreResult` in PO.BpmnEngine builds the `Error` entry
83
+ * with "the six expected fields", and separately mentions "the generic failure
84
+ * mapping shape, which includes a `response` key". Those are the two forms below —
85
+ * 243 and 42 byte-identical copies across five unrelated customer exports.
86
+ *
87
+ * Matched on exact bytes, so a contract that changes simply stops matching and the
88
+ * body is declared like any other. Nothing is coerced into the constant.
89
+ */
90
+ const WELL_KNOWN_SCHEMAS = [
91
+ ['errorSchema', JSON.stringify(errorSchema)],
92
+ ['errorSchemaWithResponse', JSON.stringify(errorSchemaWithResponse)],
93
+ ];
94
+ /** The SDK export that reproduces this body exactly, if one does. */
95
+ function wellKnownSchema(body) {
96
+ const text = String(body);
97
+ return WELL_KNOWN_SCHEMAS.find(([, json]) => json === text)?.[0];
98
+ }
99
+ /** An identifier for a shared schema, from the variable name that carries it. */
100
+ function schemaIdFrom(name, taken) {
101
+ const base = `${identifierize(name ?? 'shared')}Schema`;
102
+ let id = base;
103
+ for (let n = 2; taken.has(id); n++)
104
+ id = `${base}${n}`;
105
+ taken.add(id);
106
+ return id;
107
+ }
108
+ /** Decide the plan by counting how often each body appears across the document. */
109
+ function planSchemas(rows) {
110
+ const plan = { ref: new Map(), declare: [], imports: new Set() };
111
+ const counts = new Map();
112
+ const firstName = new Map();
113
+ for (const row of rows) {
114
+ if (row.body === undefined)
115
+ continue;
116
+ const text = String(row.body);
117
+ counts.set(text, (counts.get(text) ?? 0) + 1);
118
+ if (!firstName.has(text))
119
+ firstName.set(text, row.name);
120
+ }
121
+ const taken = new Set();
122
+ for (const [text, count] of counts) {
123
+ const known = wellKnownSchema(text);
124
+ if (known) {
125
+ plan.ref.set(text, known);
126
+ plan.imports.add(known);
127
+ }
128
+ else if (count > 1) {
129
+ const id = schemaIdFrom(firstName.get(text), taken);
130
+ plan.ref.set(text, str(id));
131
+ plan.declare.push([id, jsonLiteral(text)]);
132
+ }
133
+ }
134
+ return plan;
135
+ }
136
+ /**
137
+ * A schema body as a JSON LITERAL rather than an escaped string.
138
+ *
139
+ * `schema: { type: "object", … }` reads; `schema: "{\"type\":\"object\",…}"` does
140
+ * not, and a real artifact carries thousands of characters of it. The emitters have
141
+ * always accepted either — `typeof x === 'string' ? x : JSON.stringify(x)` — so this
142
+ * only changes which one an IMPORT produces.
143
+ *
144
+ * Only when re-stringifying reproduces the source byte for byte. A body with
145
+ * whitespace, an unusual key order, or anything JSON.parse would not give back
146
+ * unchanged keeps its string form, because the round trip is held to byte identity
147
+ * and a prettier source is not worth breaking that.
148
+ */
149
+ function jsonLiteral(body) {
150
+ const value = jsonValue(body);
151
+ return typeof value === 'string' ? str(value) : literal(value);
152
+ }
153
+ /**
154
+ * The same decision, as a VALUE rather than as source text — for a body that lands
155
+ * inside a record the emitter renders for itself, such as a node's `context`.
156
+ *
157
+ * The round-trip guard is the whole rule: a body is parsed only when
158
+ * re-stringifying it reproduces the source byte for byte, so anything with
159
+ * whitespace, an unusual key order, or a number JSON would not spell back the same
160
+ * way stays the string it was and the recompile still matches.
161
+ */
162
+ function jsonValue(body) {
163
+ const text = String(body);
164
+ try {
165
+ const parsed = JSON.parse(text);
166
+ // A body of `null` stays the STRING it was. `null` and `undefined` are how a
167
+ // context record says "field not supplied" — `contextRows` skips both — so
168
+ // handing back a real null would delete the row on recompile rather than write
169
+ // it. `Intsvc.UnifiedHttpRequest` makes this concrete: its `body` field's
170
+ // registry default is the text `null`, and every HTTP node carries it.
171
+ if (parsed === null)
172
+ return text;
173
+ return JSON.stringify(parsed) === text ? parsed : text;
174
+ }
175
+ catch {
176
+ return text;
177
+ }
178
+ }
179
+ /** An object key: a bare identifier when legal, otherwise a quoted string. */
180
+ function key(k) {
181
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : str(k);
182
+ }
183
+ /** Render `{ a: 1, b: 2 }` from the entries that are present; `''` when none are. */
184
+ function opts(entries) {
185
+ const parts = entries.filter(([, v]) => v !== undefined).map(([k, v]) => `${key(k)}: ${v}`);
186
+ return parts.length ? `{ ${parts.join(', ')} }` : '';
187
+ }
188
+ /** A record literal, or `undefined` when the record is empty. */
189
+ function record(r) {
190
+ if (!r || Object.keys(r).length === 0)
191
+ return undefined;
192
+ return `{ ${Object.entries(r)
193
+ .map(([k, v]) => `${key(k)}: ${typeof v === 'string' ? str(v) : literal(v)}`)
194
+ .join(', ')} }`;
195
+ }
196
+ /** A collector for the fluent chain's lines. */
197
+ class Chain {
198
+ indent;
199
+ lines = [];
200
+ constructor(indent) {
201
+ this.indent = indent;
202
+ }
203
+ add(call) {
204
+ this.lines.push(`${this.indent}${call}`);
205
+ }
206
+ get all() {
207
+ return this.lines;
208
+ }
209
+ }
210
+ // ─── Reading the moddle tree ──────────────────────────────────────────────────
211
+ /** `bpmn:SendTask` → `sendTask`; the instance-tag spelling the builder speaks. */
212
+ function localName(type) {
213
+ const local = type.split(':')[1] ?? type;
214
+ return local.charAt(0).toLowerCase() + local.slice(1);
215
+ }
216
+ /** The `uipath:*` payload element on an element, if it has one. */
217
+ function payloadOf(el) {
218
+ for (const ext of el.extensionElements?.values ?? []) {
219
+ if (ext.$type === 'uipath:Activity' || ext.$type === 'uipath:Event' || ext.$type === 'uipath:Mapping')
220
+ return ext;
221
+ }
222
+ return undefined;
223
+ }
224
+ /** `uipath:tags` on an element as a plain record, or undefined when it has none. */
225
+ function tagsOf(el) {
226
+ const tags = (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Tags');
227
+ const rows = tags?.tags ?? [];
228
+ // A row with no `key` is the PROCESS-level shape (a plain value), which
229
+ // `.metadata({ tags })` owns; only the keyed per-node form belongs here.
230
+ const keyed = rows.filter((t) => t.key !== undefined);
231
+ if (!keyed.length)
232
+ return undefined;
233
+ return record(Object.fromEntries(keyed.map((t) => [t.key, String(t.value ?? '')])));
234
+ }
235
+ /** The `uipath:Retry` element on an element, if it has one. */
236
+ function retryOf(el) {
237
+ return (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Retry');
238
+ }
239
+ /**
240
+ * Whether the context rows carry detail the `context` record cannot hold: a type
241
+ * the registry does not declare for that field, or an absent `value` attribute
242
+ * (which is not the same as `value=""`).
243
+ */
244
+ function contextRowsNeedSpellingOut(payload, type) {
245
+ const rows = payload?.context?.input ?? [];
246
+ if (!rows.length || !type)
247
+ return false;
248
+ let declared;
249
+ try {
250
+ declared = new Map(registryType(type).context.map((f) => [f.name, f.type]));
251
+ }
252
+ catch {
253
+ return true;
254
+ }
255
+ return rows.some((r) => (r.value === undefined && r.body === undefined) ||
256
+ r.required !== undefined ||
257
+ r.target !== undefined ||
258
+ (r.type ?? 'string') !== (declared.get(r.name) ?? 'string'));
259
+ }
260
+ /** The context rows as `TypedContextRow` option source, in document order. */
261
+ function emitContextRows(payload) {
262
+ const rows = payload?.context?.input ?? [];
263
+ if (!rows.length)
264
+ return undefined;
265
+ return `[${rows
266
+ .map((r) => opts([
267
+ ['name', str(r.name)],
268
+ ['type', r.type !== undefined ? str(r.type) : undefined],
269
+ ['required', r.required !== undefined ? 'true' : undefined],
270
+ ['target', r.target !== undefined ? str(r.target) : undefined],
271
+ ['value', r.value !== undefined ? str(r.value) : undefined],
272
+ ['body', r.body !== undefined ? (r.type === 'json' ? jsonLiteral(r.body) : str(String(r.body))) : undefined],
273
+ ]))
274
+ .join(', ')}]`;
275
+ }
276
+ /** `uipath:context` inputs as a plain record, json fields read from the body. */
277
+ function contextOf(payload) {
278
+ const out = {};
279
+ for (const input of payload?.context?.input ?? []) {
280
+ out[input.name] = input.type === 'json' ? jsonValue(input.body ?? '') : (input.value ?? '');
281
+ }
282
+ return out;
283
+ }
284
+ /** The extension type value, e.g. `Intsvc.UnifiedHttpRequest`. */
285
+ function extensionTypeOf(payload) {
286
+ return payload?.type?.value;
287
+ }
288
+ /** A retry policy back into `RetrySpec` option source. */
289
+ function emitRetry(retry) {
290
+ if (!retry)
291
+ return undefined;
292
+ const count = Number(retry.maxRetryCount ?? 0);
293
+ if (!Number.isFinite(count) || count < 1)
294
+ return undefined;
295
+ return opts([
296
+ ['maxRetries', String(count)],
297
+ ['backoff', retry.retryBackoff ? str(retry.retryBackoff) : undefined],
298
+ ['backoffType', retry.retryBackoffType === 'Exponential' ? str('exponential') : undefined],
299
+ ['exponentialBase', retry.exponentialBase !== undefined ? String(Number(retry.exponentialBase)) : undefined],
300
+ ['allErrors', retry.retryAllErrors === 'true' || retry.retryAllErrors === true ? 'true' : undefined],
301
+ ['maxDuration', retry.maxDuration ? str(retry.maxDuration) : undefined],
302
+ ]);
303
+ }
304
+ /** The `uipath:ErrorMapping` element on an element, if it has one. */
305
+ function errorMappingOf(el) {
306
+ return (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:ErrorMapping');
307
+ }
308
+ /**
309
+ * `uipath:errorMapping` back into `ErrorMappingRow[]` option source.
310
+ *
311
+ * Every attribute the descriptor declares is carried, including `priority` and
312
+ * `detail`, which neither this SDK nor the local engine interprets — an imported
313
+ * artifact may hold them and dropping them would break the byte-identity round trip.
314
+ *
315
+ * `retryable` is tri-state on the wire and stays that way here: present-and-`"true"`,
316
+ * present-and-`"false"`, or ABSENT. Reading absent as `false` would emit an attribute
317
+ * the original did not have.
318
+ */
319
+ function emitErrorMapping(em) {
320
+ const rows = em?.error ?? [];
321
+ if (!rows.length)
322
+ return undefined;
323
+ const parts = rows.map((r) => opts([
324
+ ['code', str(r.errorRef ?? '')],
325
+ ['retryable', r.retryable === undefined ? undefined : r.retryable === 'true' || r.retryable === true ? 'true' : 'false'],
326
+ ['id', r.id !== undefined ? str(r.id) : undefined],
327
+ // A numeric-looking priority round-trips as a number; anything else stays a
328
+ // string, since the wire is a string either way and the author's form is unknown.
329
+ ['priority', r.priority === undefined ? undefined : /^-?\d+$/.test(String(r.priority)) ? String(Number(r.priority)) : str(r.priority)],
330
+ ['condition', r.condition !== undefined ? str(r.condition) : undefined],
331
+ ['detail', r.detail !== undefined ? str(r.detail) : undefined],
332
+ ]));
333
+ return `[${parts.join(', ')}]`;
334
+ }
335
+ /** Multi-instance loop characteristics back into `LoopSpec` option source. */
336
+ function emitLoop(el) {
337
+ const loop = el.loopCharacteristics;
338
+ if (!loop)
339
+ return undefined;
340
+ const uip = (loop.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:LoopCharacteristics');
341
+ if (!uip)
342
+ return undefined;
343
+ return opts([
344
+ ['collection', str(uip.inputCollection ?? '')],
345
+ ['itemVar', str(uip.inputElement ?? 'item')],
346
+ ['sequential', loop.isSequential ? 'true' : undefined],
347
+ ['completion', loop.completionCondition?.body ? str(loop.completionCondition.body) : undefined],
348
+ ]);
349
+ }
350
+ /** An event definition back into the `{ message | timer | error | terminate }` option. */
351
+ function eventOptions(el) {
352
+ const def = (el.eventDefinitions ?? [])[0];
353
+ if (!def)
354
+ return [];
355
+ switch (def.$type) {
356
+ case 'bpmn:MessageEventDefinition':
357
+ return [['message', str(def.messageRef?.name ?? '')]];
358
+ case 'bpmn:TerminateEventDefinition':
359
+ return [['terminate', 'true']];
360
+ case 'bpmn:ErrorEventDefinition': {
361
+ const ref = def.errorRef;
362
+ // No `errorRef` is CATCH-ANY, not "no definition". Returning nothing here
363
+ // dropped the definition entirely, and the builder then refused the result —
364
+ // which is what made all 71 error boundaries in five real exports unimportable.
365
+ if (!ref)
366
+ return [['error', 'true']];
367
+ // The builder defaults an error's code to its name, so re-emitting the pair
368
+ // only matters when they differ — otherwise the shorter form round-trips.
369
+ return [
370
+ ['error', ref.errorCode && ref.errorCode !== ref.name ? opts([['name', str(ref.name)], ['code', str(ref.errorCode)]]) : str(ref.name)],
371
+ ];
372
+ }
373
+ case 'bpmn:TimerEventDefinition': {
374
+ const d = def.timeDuration?.body;
375
+ const date = def.timeDate?.body;
376
+ const cycle = def.timeCycle?.body;
377
+ // A bare duration is the shorthand the builder accepts directly.
378
+ if (d !== undefined && date === undefined && cycle === undefined)
379
+ return [['timer', str(d)]];
380
+ return [['timer', opts([['duration', d !== undefined ? str(d) : undefined], ['date', date !== undefined ? str(date) : undefined], ['cycle', cycle !== undefined ? str(cycle) : undefined]])]];
381
+ }
382
+ default:
383
+ return [];
384
+ }
385
+ }
386
+ /** `uipath:output` rows split into the type's own row and the author-mapped extras. */
387
+ function outputsOf(payload, id, type) {
388
+ const rows = payload?.output ?? [];
389
+ const extras = {};
390
+ let outputVar;
391
+ const specName = type ? registryType(type).output?.name : undefined;
392
+ for (const row of rows) {
393
+ if (specName !== undefined && row.name === specName) {
394
+ // Only worth emitting when it differs from the default the builder derives.
395
+ outputVar = row.var;
396
+ continue;
397
+ }
398
+ if (row.source !== undefined)
399
+ extras[row.var ?? row.name] = row.source;
400
+ }
401
+ return { outputVar, extras };
402
+ }
403
+ /**
404
+ * Whether these rows can be reproduced by the compact `outputVar` + `outputs` pair,
405
+ * or need spelling out.
406
+ *
407
+ * A row with a BODY cannot: `outputs` is a variable→expression map, so the body — a
408
+ * connector's response schema, the biggest thing in the element — has nowhere to go.
409
+ * A row whose NAME differs from the variable it writes cannot either, for the same
410
+ * reason `add_output` needed the merge: `outputs` is keyed by the variable, so it
411
+ * always writes `name` and `var` the same.
412
+ *
413
+ * Deciding per artifact rather than per type is what keeps this honest. The compact
414
+ * form stays in use wherever it is exact, so the 30 other extension types decompile
415
+ * to exactly the source they did before, and only a row that genuinely needs the
416
+ * long form gets it.
417
+ */
418
+ function outputRowsNeedSpellingOut(payload) {
419
+ const rows = payload?.output ?? [];
420
+ return rows.some((row) => row.body !== undefined ||
421
+ row.custom !== undefined ||
422
+ row.description !== undefined ||
423
+ row.target !== undefined ||
424
+ (row.var !== undefined && row.var !== row.name));
425
+ }
426
+ /** The output rows as `TypedOutputRow` option source, in document order. */
427
+ function emitOutputRows(payload) {
428
+ const rows = payload?.output ?? [];
429
+ if (!rows.length)
430
+ return undefined;
431
+ const rendered = rows.map((row) => opts([
432
+ ['name', str(row.name)],
433
+ ['type', row.type !== undefined ? str(row.type) : undefined],
434
+ ['var', row.var !== undefined ? str(row.var) : undefined],
435
+ ['source', row.source !== undefined ? str(row.source) : undefined],
436
+ ['custom', row.custom ? 'true' : undefined],
437
+ ['description', row.description !== undefined ? str(row.description) : undefined],
438
+ ['target', row.target !== undefined ? str(row.target) : undefined],
439
+ // The body goes back as the STRING it was, not as a re-serialized object: a
440
+ // schema is carried, never interpreted, so nothing here can reorder its keys
441
+ // or drop a field this SDK does not know about.
442
+ ['schema', row.body !== undefined ? jsonLiteral(row.body) : undefined],
443
+ ]));
444
+ return `[${rendered.join(', ')}]`;
445
+ }
446
+ // ─── Element emitters ─────────────────────────────────────────────────────────
447
+ /**
448
+ * A registry-typed node.
449
+ *
450
+ * Emitted through the GENERIC `.activity()` rather than a typed method, and
451
+ * deliberately: `context` is reproduced verbatim, so the round-trip is exact for
452
+ * all 30 extension types without a per-type reverse mapping to keep in step with
453
+ * the registry. Typed methods are for authoring; fidelity is what an importer
454
+ * owes you.
455
+ */
456
+ function emitTypedNode(chain, el, payload, type) {
457
+ const spec = registryType(type);
458
+ const id = el.id;
459
+ // Verbatim: reproduce every context field, in document order, and let the
460
+ // emitter inject nothing. Stripping the ones that match a registry default
461
+ // would be smaller source but a lossy round-trip — the artifact's own emitter
462
+ // may not have written the same set the registry would default.
463
+ const ctx = contextOf(payload);
464
+ const inputRows = payload.input ?? [];
465
+ let inputs;
466
+ if (spec.inputPattern === 'separateInputs') {
467
+ // A row's value is in its BODY or its `value` attribute, and which one is
468
+ // decided by the field's declared type — see `payloadInput` in typed-node.ts.
469
+ // Reading only the body silently produced `{}` for every scalar row, so a
470
+ // string correlation reference round-tripped to `value="[object Object]"`.
471
+ // Nothing before this authored a separateInputs type with a declared scalar
472
+ // field, so every row really was a body and the bug had nowhere to appear.
473
+ inputs = Object.fromEntries(inputRows.map((r) => [r.name, r.body !== undefined ? safeJson(r.body) : r.value]));
474
+ }
475
+ else if (inputRows.length) {
476
+ inputs = safeJson(inputRows[0].body);
477
+ }
478
+ const verbatimContext = contextRowsNeedSpellingOut(payload, type) ? emitContextRows(payload) : undefined;
479
+ const { outputVar, extras } = outputsOf(payload, id, type);
480
+ const defaultVar = spec.output ? `${id}_${identifierize(spec.output.name)}` : undefined;
481
+ const verbatimRows = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
482
+ chain.add(`.activity(${str(id)}, ${str(type)}${optionalArg(opts([
483
+ ['name', el.name !== undefined ? str(el.name) : undefined],
484
+ // Only when it differs from the registry's own template, so an
485
+ // SDK-authored node keeps the shorter source it had.
486
+ ['typeVersion', payload.type?.version && payload.type.version !== 'v1' ? str(payload.type.version) : undefined],
487
+ ['context', verbatimContext ? undefined : record(ctx)],
488
+ ['contextRows', verbatimContext],
489
+ ['inputSchema', payload.context?.inputSchema?.body !== undefined ? jsonLiteral(payload.context.inputSchema.body) : undefined],
490
+ ['inputs', record(inputs)],
491
+ ['outputRows', verbatimRows],
492
+ ['outputVar', verbatimRows === undefined && outputVar !== undefined && outputVar !== defaultVar ? str(outputVar) : undefined],
493
+ ['outputs', verbatimRows === undefined ? record(extras) : undefined],
494
+ ['skipCondition', payload.skipCondition !== undefined ? str(payload.skipCondition) : undefined],
495
+ ['contextVerbatim', Object.keys(ctx).length ? 'true' : undefined],
496
+ ['tags', tagsOf(el)],
497
+ ['retry', emitRetry(retryOf(el))],
498
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
499
+ ['loop', emitLoop(el)],
500
+ ]))})`);
501
+ }
502
+ /** Mirrors the emitter's own default-variable naming, so a default is not echoed. */
503
+ function identifierize(name) {
504
+ const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
505
+ if (words.length === 0)
506
+ return 'response';
507
+ return words.map((w, i) => (i === 0 ? w.charAt(0).toLowerCase() + w.slice(1) : w.charAt(0).toUpperCase() + w.slice(1))).join('');
508
+ }
509
+ /** `, { … }` when there are options, `''` when there are none. */
510
+ function optionalArg(rendered) {
511
+ return rendered ? `, ${rendered}` : '';
512
+ }
513
+ /** Parse a CDATA body as JSON, falling back to the raw string. */
514
+ function safeJson(body) {
515
+ if (typeof body !== 'string')
516
+ return {};
517
+ try {
518
+ return JSON.parse(body);
519
+ }
520
+ catch {
521
+ return body;
522
+ }
523
+ }
524
+ /** A `uipath:mapping` script task. */
525
+ function emitScriptTask(chain, el, payload, type) {
526
+ const args = (payload?.input ?? []).find((r) => r.name === 'args');
527
+ const inputs = args ? safeJson(args.body) : undefined;
528
+ const outputs = {};
529
+ for (const row of payload?.output ?? [])
530
+ outputs[row.var ?? row.name] = row.source;
531
+ // The rows go back verbatim whenever `outputs` could not reproduce them —
532
+ // the product names a row after what the script RETURNS (`scriptResponse`,
533
+ // `Error`) while binding a differently-named variable, and the derived form
534
+ // names it after the variable.
535
+ const verbatim = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
536
+ chain.add(`.scriptTask(${str(el.id)}, ${opts([
537
+ ['name', el.name !== undefined ? str(el.name) : undefined],
538
+ ['script', str(el.script ?? '')],
539
+ ['scriptFormat', el.scriptFormat && el.scriptFormat !== 'JavaScript' ? str(el.scriptFormat) : undefined],
540
+ // Only when it is neither the contract's value nor the invalid one this SDK
541
+ // used to emit. `BPMN.ScriptTask` is NORMALISED AWAY rather than preserved:
542
+ // the runtime contract has no such type, so reproducing it would propagate a
543
+ // defect `uip maestro bpmn validate` does not catch. Same call the round trip
544
+ // already makes for a bare `<uipath:input name="args">`.
545
+ [
546
+ 'type',
547
+ type !== undefined && type !== 'BPMN.Variables' && type !== 'BPMN.ScriptTask' ? str(type) : undefined,
548
+ ],
549
+ ['inputSchema', payload?.context?.inputSchema?.body !== undefined ? jsonLiteral(payload.context.inputSchema.body) : undefined],
550
+ ['inputs', record(inputs)],
551
+ ['outputs', verbatim ? undefined : record(outputs)],
552
+ ['outputRows', verbatim],
553
+ ['tags', tagsOf(el)],
554
+ ['retry', emitRetry(retryOf(el))],
555
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
556
+ ['loop', emitLoop(el)],
557
+ ])})`);
558
+ }
559
+ /** A `uipath:mapping` variable task. */
560
+ function emitVariableTask(chain, el, payload) {
561
+ const set = {};
562
+ for (const row of payload.output ?? []) {
563
+ const source = String(row.source ?? '');
564
+ // The serializer wraps a literal as `=js:"…"`; unwrap it so the source reads
565
+ // the way it was authored rather than the way it was encoded.
566
+ const asLiteral = /^=js:(".*")$/.exec(source);
567
+ set[row.var ?? row.name] = asLiteral ? JSON.parse(asLiteral[1]) : source;
568
+ }
569
+ chain.add(`.task(${str(el.id)}, ${opts([
570
+ ['name', el.name !== undefined ? str(el.name) : undefined],
571
+ ['set', record(set)],
572
+ ['tags', tagsOf(el)],
573
+ ['retry', emitRetry(retryOf(el))],
574
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
575
+ ['loop', emitLoop(el)],
576
+ ])})`);
577
+ }
578
+ /** Every element of one scope, in document order, then its flows. */
579
+ function emitScope(chain, container, depth, indent) {
580
+ const flows = [];
581
+ for (const el of container.flowElements ?? []) {
582
+ if (el.$type === 'bpmn:SequenceFlow') {
583
+ flows.push(el);
584
+ continue;
585
+ }
586
+ emitElement(chain, el, depth, indent);
587
+ }
588
+ for (const f of flows) {
589
+ const auto = `Flow_${f.sourceRef?.id}_${f.targetRef?.id}`;
590
+ chain.add(`.sequenceFlow(${str(f.sourceRef?.id)}, ${str(f.targetRef?.id)}${optionalArg(opts([
591
+ ['id', f.id !== auto ? str(f.id) : undefined],
592
+ ['name', f.name !== undefined ? str(f.name) : undefined],
593
+ ['condition', f.conditionExpression?.body !== undefined ? str(f.conditionExpression.body) : undefined],
594
+ ]))})`);
595
+ }
596
+ }
597
+ /**
598
+ * Whether a start event's event definition carries configuration a registry spec
599
+ * cannot supply — today, a timer's duration/date/cycle.
600
+ *
601
+ * A definition with no children is reproduced exactly by the spec-driven emitter
602
+ * (it emits the same bare element), so the generic `.activity()` path is lossless
603
+ * for it. One with a body is not.
604
+ */
605
+ function startEventDefinitionCarriesConfig(el) {
606
+ for (const def of el.eventDefinitions ?? []) {
607
+ if (def.$type === 'bpmn:TimerEventDefinition')
608
+ return true;
609
+ }
610
+ return false;
611
+ }
612
+ /**
613
+ * A start event carrying a registry-backed trigger — `.startEvent({ timer, trigger })`.
614
+ *
615
+ * The schedule comes from the element's own event definition and the payload
616
+ * variable from the extension's output row, which is exactly the split the builder
617
+ * models: the definition schedules, the extension names the type and the variable.
618
+ *
619
+ * Only `Intsvc.TimerTrigger` has an authoring surface today. Another start-event
620
+ * type reaching here (`Intsvc.EventTrigger`, phase 3d) has no `trigger` spelling to
621
+ * emit, so it is refused rather than quietly written as an `.activity()` that would
622
+ * lose its event definition.
623
+ */
624
+ function emitTriggerStartEvent(chain, el, payload, type) {
625
+ if (type !== 'Intsvc.TimerTrigger') {
626
+ throw new Error(`start event "${el.id}" carries extension type "${type}", whose event definition the generic ` +
627
+ `.activity() path cannot reproduce, so it is refused rather than silently emitted without it.`);
628
+ }
629
+ const spec = registryType(type);
630
+ const row = (payload.output ?? [])[0];
631
+ const defaultVar = spec.output ? `${el.id}_${identifierize(spec.output.name)}` : undefined;
632
+ const outputVar = row?.var;
633
+ const trigger = outputVar !== undefined && outputVar !== defaultVar ? opts([['outputVar', str(outputVar)]]) : 'true';
634
+ chain.add(`.startEvent(${str(el.id)}, ${opts([
635
+ ['name', el.name !== undefined ? str(el.name) : undefined],
636
+ ...eventOptions(el),
637
+ ['trigger', trigger],
638
+ ])})`);
639
+ }
640
+ /**
641
+ * Kinds whose builder method OWNS the element it emits, so a payload that does not
642
+ * describe that element is a decoration rather than the node itself.
643
+ *
644
+ * An activity is deliberately absent: for a service task the extension type really
645
+ * is what the node is, and the generic `.activity()` reproduces it faithfully.
646
+ */
647
+ const ELEMENT_OWNS = new Set([
648
+ 'startEvent',
649
+ 'endEvent',
650
+ 'intermediateCatchEvent',
651
+ 'intermediateThrowEvent',
652
+ 'boundaryEvent',
653
+ 'scriptTask',
654
+ 'subProcess',
655
+ ]);
656
+ /** The five element kinds the builder spells with an event method. */
657
+ const EVENT_KINDS = new Set([
658
+ 'startEvent',
659
+ 'endEvent',
660
+ 'intermediateCatchEvent',
661
+ 'intermediateThrowEvent',
662
+ 'boundaryEvent',
663
+ ]);
664
+ /**
665
+ * Whether an element is the kind its extension type says it is.
666
+ *
667
+ * The registry gives each type the element it serializes to. When the two agree,
668
+ * the type describes the whole element and the typed emitter reproduces it. When
669
+ * they disagree the payload is a DECORATION on an element the type knows nothing
670
+ * about — `BPMN.Variables` says `bpmn:Task`, but the product writes it on start
671
+ * events, end events and sub-processes too — and the element has to win.
672
+ *
673
+ * An unknown type answers `false` rather than throwing: the caller is choosing an
674
+ * emitter, and the typed path is the one that reports the unknown type properly.
675
+ */
676
+ function typeDescribesElement(kind, type) {
677
+ if (!type)
678
+ return false;
679
+ let element;
680
+ try {
681
+ element = registryType(type).element;
682
+ }
683
+ catch {
684
+ return false;
685
+ }
686
+ return !!element && localName(element).toLowerCase() === kind.toLowerCase();
687
+ }
688
+ /**
689
+ * An event element, with whatever payload it carries as an option on it.
690
+ *
691
+ * One emitter for all five kinds because they differ only in which options are
692
+ * legal, and the boundary's extra two are additive.
693
+ */
694
+ function emitEvent(chain, el, kind, payload, type) {
695
+ // `outputRows` is the only payload shape an event carries in the corpus. A
696
+ // `context` or `input` row on one would mean the registry describes the element
697
+ // after all, which is the branch this function is not on.
698
+ const eventPayload = payload && type
699
+ ? opts([
700
+ ['type', str(type)],
701
+ ['outputRows', emitOutputRows(payload) ?? '[]'],
702
+ ])
703
+ : undefined;
704
+ if (kind === 'boundaryEvent') {
705
+ chain.add(`.boundaryEvent(${str(el.id)}, ${opts([
706
+ ['name', el.name !== undefined ? str(el.name) : undefined],
707
+ ['attachedTo', str(el.attachedToRef?.id)],
708
+ // The builder defaults to interrupting; only the exception is worth saying.
709
+ ['cancelActivity', el.cancelActivity === false ? 'false' : undefined],
710
+ ...eventOptions(el),
711
+ ['payload', eventPayload],
712
+ ])})`);
713
+ return;
714
+ }
715
+ chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([
716
+ ['name', el.name !== undefined ? str(el.name) : undefined],
717
+ ...eventOptions(el),
718
+ ['payload', eventPayload],
719
+ ]))})`);
720
+ }
721
+ function emitElement(chain, el, depth, indent) {
722
+ const kind = localName(el.$type);
723
+ const payload = payloadOf(el);
724
+ const type = extensionTypeOf(payload);
725
+ // THE ELEMENT DECIDES, whenever the payload does not describe it.
726
+ //
727
+ // The product decorates elements with payloads that say nothing about what the
728
+ // element is: `BPMN.Variables` on a start event (mapping process inputs onto
729
+ // variables), on an end event (variables onto process outputs), on a script task,
730
+ // on a sub-process — and `uipath:Activity` on an error boundary, to capture the
731
+ // error payload. `BPMN.Variables`' registry element is `bpmn:Task`, so dispatching
732
+ // on the extension type turned every one of them into a task. Measured over five
733
+ // real exports, that cost a process its only start event (recompile then failed
734
+ // `NO_START`), freed ten error boundaries into the main flow as service tasks,
735
+ // dropped 19 445 lines of script body, and flattened a sub-process holding 22
736
+ // elements into a single `.task()`. All of it validates.
737
+ //
738
+ // `typeDescribesElement` separates decoration from description, rather than a list
739
+ // of decorating types: `Intsvc.EventTrigger` and `Intsvc.TimerTrigger` genuinely
740
+ // ARE start events in the registry, so they keep the typed path and their context.
741
+ const elementWins = ELEMENT_OWNS.has(kind) && !typeDescribesElement(kind, type);
742
+ if (elementWins) {
743
+ if (EVENT_KINDS.has(kind))
744
+ return emitEvent(chain, el, kind, payload, type);
745
+ if (kind === 'scriptTask')
746
+ return emitScriptTask(chain, el, payload, type);
747
+ // `subProcess` falls through to the switch below, whose emitter already
748
+ // recurses into the nested scope — it was simply never reached.
749
+ }
750
+ // Registry-typed nodes: the extension type, not the element, decides.
751
+ if (payload && type && !elementWins) {
752
+ if (type === 'BPMN.ScriptTask')
753
+ return emitScriptTask(chain, el, payload, type);
754
+ if (type === 'BPMN.Variables')
755
+ return emitVariableTask(chain, el, payload);
756
+ // …except a start-event type whose event definition carries CONFIGURATION the
757
+ // generic `.activity()` path would drop.
758
+ //
759
+ // The distinction is the definition's CONTENT, not the element. A timer's
760
+ // definition holds the schedule, which the typed emitter takes from the spec —
761
+ // and the spec declares it empty, so a timer trigger came back as
762
+ // `<bpmn:timerEventDefinition />` with no schedule at all: the fires-never
763
+ // artifact the builder refuses to author, reachable by IMPORT instead. Both
764
+ // validators call that Valid, so only the byte-identity gate sees it.
765
+ //
766
+ // `Intsvc.EventTrigger` (phase 3d) is the opposite case: its
767
+ // `<bpmn:messageEventDefinition />` is BARE, and the spec emits exactly that, so
768
+ // the generic path reproduces it byte-for-byte and needs no special handling.
769
+ if (kind === 'startEvent' && startEventDefinitionCarriesConfig(el)) {
770
+ return emitTriggerStartEvent(chain, el, payload, type);
771
+ }
772
+ return emitTypedNode(chain, el, payload, type);
773
+ }
774
+ switch (kind) {
775
+ case 'exclusiveGateway':
776
+ case 'inclusiveGateway':
777
+ chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([
778
+ ['name', el.name !== undefined ? str(el.name) : undefined],
779
+ ['default', el.default?.id !== undefined ? str(el.default.id) : undefined],
780
+ ]))})`);
781
+ return;
782
+ case 'parallelGateway':
783
+ case 'eventBasedGateway':
784
+ chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([['name', el.name !== undefined ? str(el.name) : undefined]]))})`);
785
+ return;
786
+ // A task element with no `uipath:*` payload — an abstract task. All seven kinds are
787
+ // legal bare and `validate` accepts every one, so they decompile through
788
+ // `.plainTask()`, which is the only surface that emits a payload-free element.
789
+ //
790
+ // `bpmn:task` used to come through `.task()` here, with a comment claiming it
791
+ // "round-trips as an empty one". It did not: `.task()` always emits a
792
+ // `BPMN.Variables` mapping, so the recompiled element GAINED a payload the original
793
+ // never had — a silent change, in a file whose header promises the opposite. The
794
+ // other six threw "has no builder equivalent", which was at least honest.
795
+ case 'task':
796
+ case 'userTask':
797
+ case 'serviceTask':
798
+ case 'sendTask':
799
+ case 'manualTask':
800
+ case 'businessRuleTask':
801
+ case 'receiveTask':
802
+ chain.add(`.plainTask(${str(el.id)}${optionalArg(opts([
803
+ ['name', el.name !== undefined ? str(el.name) : undefined],
804
+ // Only when it is not the default, so the common case stays terse. Spelled
805
+ // the way the XML is, not the way moddle types it.
806
+ ['element', kind === 'task' ? undefined : str(`bpmn:${kind}`)],
807
+ ['tags', tagsOf(el)],
808
+ ['retry', emitRetry(retryOf(el))],
809
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
810
+ ['loop', emitLoop(el)],
811
+ ]))})`);
812
+ return;
813
+ case 'subProcess': {
814
+ const inner = new Chain(indent.repeat(depth + 2));
815
+ emitScope(inner, el, depth + 1, indent);
816
+ emitVariablesInto(inner, el);
817
+ const body = inner.all.length
818
+ ? `(sp) => {\n${inner.all.map((l, i) => (i === 0 ? `${l.replace(/^(\s*)\./, '$1sp.')}` : l)).join('\n')};\n${indent.repeat(depth + 1)}}`
819
+ : '(sp) => {}';
820
+ // The mapping a sub-process carries maps its results back out to the
821
+ // enclosing scope — distinct from the variables it DECLARES, which
822
+ // `emitVariablesInto` has already written into the body above.
823
+ const spPayload = payloadOf(el);
824
+ const spType = extensionTypeOf(spPayload);
825
+ chain.add(`.subProcess(${str(el.id)}, ${body}${optionalArg(opts([
826
+ ['name', el.name !== undefined ? str(el.name) : undefined],
827
+ [
828
+ 'payload',
829
+ spPayload && spType
830
+ ? opts([
831
+ ['type', str(spType)],
832
+ ['outputRows', emitOutputRows(spPayload) ?? '[]'],
833
+ ])
834
+ : undefined,
835
+ ],
836
+ ['triggeredByEvent', el.triggeredByEvent ? 'true' : undefined],
837
+ ['loop', emitLoop(el)],
838
+ ['tags', tagsOf(el)],
839
+ ['retry', emitRetry(retryOf(el))],
840
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
841
+ ]))})`);
842
+ return;
843
+ }
844
+ default:
845
+ throw new Error(`decompile: element <${el.$type}> (id "${el.id}") has no builder equivalent. ` +
846
+ 'A registry-typed node needs its `uipath:activity`/`uipath:event`/`uipath:mapping` payload to be decompiled.');
847
+ }
848
+ }
849
+ /** The core `types` spellings the builder's `.var()` takes, from a wire value. */
850
+ function varType(wire) {
851
+ switch (wire) {
852
+ case 'json':
853
+ // `object` and `array` both serialize to `json`; `object` is the safe reading
854
+ // since the builder maps both the same way.
855
+ return 'object';
856
+ case 'bool':
857
+ return 'boolean';
858
+ default:
859
+ return wire ?? 'string';
860
+ }
861
+ }
862
+ /** Emit `.var()` / `.input()` / `.output()` for one scope's declarations. */
863
+ function emitVariablesInto(chain, container, skip = new Set(),
864
+ /**
865
+ * Rewrite one `elementId` as another. Needed when the declarations are read off a
866
+ * COLLABORATION, whose id will not exist after a recompile — see `variablesHost`.
867
+ */
868
+ rehome,
869
+ /** How each schema body should be written — see {@link planSchemas}. */
870
+ plan) {
871
+ const vars = (container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
872
+ if (!vars)
873
+ return;
874
+ const groups = [
875
+ ['input', 'input'],
876
+ ['inputOutput', 'var'],
877
+ ['output', 'output'],
878
+ ];
879
+ for (const [prop, method] of groups) {
880
+ for (const v of vars[prop] ?? []) {
881
+ if (skip.has(v.id))
882
+ continue;
883
+ const type = varType(v.type);
884
+ const def = v.default === undefined ? undefined : decodeDefault(v.default, v.type);
885
+ chain.add(`.${method}(${str(v.id)}, ${str(type)}${optionalArg(opts([
886
+ ['name', v.name !== undefined && v.name !== v.id ? str(v.name) : undefined],
887
+ ['default', def],
888
+ ['schema', v.body !== undefined ? (plan?.ref.get(String(v.body)) ?? jsonLiteral(v.body)) : undefined],
889
+ ['required', v.required !== undefined ? 'true' : undefined],
890
+ [
891
+ 'elementId',
892
+ v.elementId === undefined
893
+ ? undefined
894
+ : str(rehome && v.elementId === rehome.from ? rehome.to : v.elementId),
895
+ ],
896
+ ]))})`);
897
+ }
898
+ }
899
+ }
900
+ /** A variable `default` attribute back into the TS literal it came from. */
901
+ function decodeDefault(raw, wire) {
902
+ if (wire === 'json')
903
+ return raw; // already JSON — emit it verbatim
904
+ if (wire === 'number')
905
+ return Number.isFinite(Number(raw)) ? String(Number(raw)) : str(raw);
906
+ if (wire === 'boolean' || wire === 'bool')
907
+ return raw === 'true' ? 'true' : 'false';
908
+ return str(raw);
909
+ }
910
+ // ─── Entry point ──────────────────────────────────────────────────────────────
911
+ /**
912
+ * Turn `.bpmn` XML into `.bpmn.ts` builder source.
913
+ *
914
+ * @param xml - The `.bpmn` document.
915
+ * @param options - The import specifier for the emitted source.
916
+ * @returns TypeScript source whose default export re-serializes to the same XML.
917
+ */
918
+ /**
919
+ * Where a document's `uipath:variables` and `uipath:bindings` actually live, and
920
+ * what it costs to read them from there.
921
+ *
922
+ * A pool-shaped export hangs both off the `bpmn:collaboration` rather than the
923
+ * `bpmn:process` — the process is reached through a `bpmn:participant` — and the
924
+ * SDK models a bare process. Reading only the process found nothing at all: one
925
+ * real customer file declares **all 112** of its variables at collaboration level,
926
+ * so the import produced a source whose every expression referenced something
927
+ * undeclared.
928
+ *
929
+ * Flattening is the honest trade — the variables are the process's, and the pool
930
+ * is presentation — but it is not free, so it is announced rather than performed
931
+ * silently: the emitted source carries a header saying what was dropped. Twenty-seven
932
+ * of those variables are scoped to the collaboration's own id, which will not exist
933
+ * after a recompile, so they are rehomed onto the process, which is a legal scope.
934
+ */
935
+ function variablesHost(defs, process) {
936
+ const hasOwn = (process.extensionElements?.values ?? []).some((x) => x.$type === 'uipath:Variables' || x.$type === 'uipath:Bindings');
937
+ const collab = (defs.rootElements ?? []).find((r) => r.$type === 'bpmn:Collaboration');
938
+ if (hasOwn || !collab)
939
+ return { container: process, note: '' };
940
+ const lanes = (process.laneSets ?? []).flatMap((ls) => ls.lanes ?? []);
941
+ const dropped = [
942
+ `bpmn:collaboration ${collab.id}`,
943
+ ...(collab.participants ?? []).map((p) => `bpmn:participant ${p.id}${p.name ? ` (${p.name})` : ''}`),
944
+ ...lanes.map((l) => `bpmn:lane ${l.id}${l.name ? ` (${l.name})` : ''}`),
945
+ ];
946
+ const note = `// FLATTENED FROM A POOL. The source document wrapped this process in a\n` +
947
+ `// collaboration, and the SDK models a bare bpmn:process — so recompiling this\n` +
948
+ `// file will NOT reproduce the pool or its lanes, and the canvas will look\n` +
949
+ `// different. Dropped:\n` +
950
+ dropped.map((d) => `// - ${d}\n`).join('') +
951
+ `// The variables below were declared on the collaboration and are carried over;\n` +
952
+ `// those scoped to it are rehomed onto the process, which is a legal scope.\n\n`;
953
+ return { container: collab, rehome: { from: collab.id, to: process.id }, note };
954
+ }
955
+ export async function decompile(xml, options = {}) {
956
+ const moddle = new BpmnModdle({ uipath: uipathDescriptor });
957
+ const { rootElement } = await moddle.fromXML(xml, { lax: true });
958
+ const defs = rootElement;
959
+ const process = (defs.rootElements ?? []).find((r) => r.$type === 'bpmn:Process');
960
+ if (!process)
961
+ throw new Error('decompile: no bpmn:Process in the document');
962
+ const indent = ' ';
963
+ const chain = new Chain(indent);
964
+ const host = variablesHost(defs, process);
965
+ // Process metadata, before anything else — it describes the document, and reading it
966
+ // first mirrors the order `serialize` writes it.
967
+ //
968
+ // Dropping it silently is a round-trip hole with teeth: every real artifact carries
969
+ // `uipath:migrationVersion` (the skills `edit/*` fixtures all have version="11.5"), and a
970
+ // grader that requires it fails a decompiled-then-recompiled process even though the
971
+ // author changed nothing. `.metadata()` only became expressible in #474, which is why
972
+ // this was not wired up when the importer landed.
973
+ const processExt = process.extensionElements?.values ?? [];
974
+ const findExt = (type) => processExt.find((x) => x.$type === type);
975
+ const migration = findExt('uipath:MigrationVersion');
976
+ const rootStart = (process.flowElements ?? []).find((x) => x.$type === 'bpmn:StartEvent');
977
+ const entryPoint = (rootStart?.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:EntryPointId') ?? findExt('uipath:EntryPointId');
978
+ const tagsExt = findExt('uipath:Tags');
979
+ const caseMgmt = findExt('uipath:CaseManagement');
980
+ const tagValues = (tagsExt?.tags ?? []).map((t) => t.value).filter((v) => typeof v === 'string');
981
+ // `isExecutable` is an ATTRIBUTE rather than an extension, and only worth emitting when
982
+ // it is false: the serializer defaults to true, and a document that omits the attribute
983
+ // is executable by BPMN's own default. Carrying it matters because the serializer used
984
+ // to hardcode true, so a non-executable process came back executable — a draft promoted
985
+ // to runnable, silently.
986
+ const nonExecutable = process.isExecutable === false;
987
+ // The schema generation gets its OWN call, under the name all three families
988
+ // use for a document's schema version — and it comes first, mirroring the
989
+ // order `serialize` writes the process's extension children. `.metadata({
990
+ // migrationVersion })` still sets the same field; this is the spelling the
991
+ // round-trip emits.
992
+ const schema = preserveSchemaVersion(BPMN_SCHEMA_VERSIONS, migration?.version);
993
+ if (schema.call)
994
+ chain.add(schema.call);
995
+ if (entryPoint !== undefined ||
996
+ tagValues.length > 0 ||
997
+ caseMgmt !== undefined ||
998
+ nonExecutable) {
999
+ chain.add(`.metadata(${opts([
1000
+ ['executable', nonExecutable ? 'false' : undefined],
1001
+ ['entryPointId', entryPoint?.value !== undefined ? str(entryPoint.value) : undefined],
1002
+ ['tags', tagValues.length > 0 ? `[${tagValues.map((t) => str(t)).join(', ')}]` : undefined],
1003
+ [
1004
+ 'caseManagement',
1005
+ caseMgmt?.version !== undefined
1006
+ ? `{ version: ${str(caseMgmt.version)}${caseMgmt.value !== undefined ? `, value: ${str(caseMgmt.value)}` : ''} }`
1007
+ : undefined,
1008
+ ],
1009
+ ])})`);
1010
+ }
1011
+ // Bindings next: a typed node may reference one, and reading the declarations
1012
+ // before the nodes keeps the emitted source in dependency order.
1013
+ const bindings = (host.container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Bindings');
1014
+ for (const b of bindings?.binding ?? []) {
1015
+ chain.add(`.binding(${str(b.id)}, ${opts([
1016
+ ['name', b.name !== undefined && b.name !== b.id ? str(b.name) : undefined],
1017
+ ['value', b.default !== undefined ? str(b.default) : undefined],
1018
+ ['resource', b.resource !== undefined && b.resource !== 'custom' ? str(b.resource) : undefined],
1019
+ ['resourceKey', b.resourceKey !== undefined && b.resourceKey !== b.default ? str(b.resourceKey) : undefined],
1020
+ ['propertyAttribute', b.propertyAttribute !== undefined && b.propertyAttribute !== 'value' ? str(b.propertyAttribute) : undefined],
1021
+ ['resourceSubType', b.resourceSubType !== undefined ? str(b.resourceSubType) : undefined],
1022
+ ])})`);
1023
+ }
1024
+ // Variables the serializer DERIVES from a node's output are re-derived on
1025
+ // recompile, so echoing them would double-declare. Skip exactly those.
1026
+ const derived = derivedVariableIds(process);
1027
+ // Count the bodies first: whether one is written inline or shared depends on how
1028
+ // many variables carry it, which is only knowable across the whole document.
1029
+ const varsExt = (host.container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
1030
+ const schemaPlan = planSchemas([
1031
+ ...(varsExt?.input ?? []),
1032
+ ...(varsExt?.inputOutput ?? []),
1033
+ ...(varsExt?.output ?? []),
1034
+ ]);
1035
+ for (const [id, body] of schemaPlan.declare)
1036
+ chain.add(`.schema(${str(id)}, ${body})`);
1037
+ emitVariablesInto(chain, host.container, derived, host.rehome, schemaPlan);
1038
+ emitScope(chain, process, 0, indent);
1039
+ const importFrom = options.importSpecifier ?? './bpmn-sdk.js';
1040
+ const imported = ['bpmn', ...[...schemaPlan.imports].sort()].join(', ');
1041
+ const head = `import { ${imported} } from ${str(importFrom)};\n\nexport default bpmn(${str(process.id)})`;
1042
+ const name = process.name !== undefined ? `\n${indent}.name(${str(process.name)})` : '';
1043
+ return `${host.note}${head}${name}\n${chain.all.join('\n')}\n${indent}.build();\n`;
1044
+ }
1045
+ /**
1046
+ * Variable ids the serializer creates on its own from a node's output rows.
1047
+ *
1048
+ * These are not author declarations — `implicitNodeVars` adds them at serialize
1049
+ * time — so a decompiled file must NOT re-declare them, or the recompiled process
1050
+ * declares them twice and the XML stops matching.
1051
+ *
1052
+ * **This set has to equal serialize's derive set exactly.** Drift either way is a
1053
+ * silent round-trip bug: too narrow and a variable is declared twice (measured —
1054
+ * an author-mapped HITL output was re-declared, which reordered the block); too
1055
+ * wide and a genuinely authored variable disappears. So it mirrors
1056
+ * `implicitNodeVars`: EVERY output row of a typed node, its own and the
1057
+ * author-mapped extras alike, plus a connector's response/error pair — but only
1058
+ * where the declaration on disk actually LOOKS derived, per below.
1059
+ *
1060
+ * The second half of that comment was not hypothetical. `implicitNodeVars` claims a
1061
+ * row's variable only when the author has not declared it (`taken.has(id)` — "author-
1062
+ * declared names win"), so a row whose `var` is also an explicit `.var()` is NOT
1063
+ * derived, and echoing the row's id into the skip set deleted a real declaration.
1064
+ * Measured on a business rule mapping into a declared variable:
1065
+ * `.var('t', 'string', { default: 'none' })` vanished and came back as the implicit
1066
+ * `type="json"` with no default — a silently RETYPED variable, D1's hazard arriving by
1067
+ * a different road. Any `outputs: { <a declared var>: … }` on any typed node hit it;
1068
+ * the HITL case in the round-trip table missed it only because its mapped row writes
1069
+ * `decision` while its declared variable is `o`.
1070
+ */
1071
+ function derivedVariableIds(process) {
1072
+ const candidates = new Set();
1073
+ const walk = (container) => {
1074
+ for (const el of container.flowElements ?? []) {
1075
+ const payload = payloadOf(el);
1076
+ const type = extensionTypeOf(payload);
1077
+ // A mapping node's rows write AUTHORED variables (a script task's outputs, a
1078
+ // variable task's assignments), so they are not derived.
1079
+ if (payload && type && type !== 'BPMN.Variables' && type !== 'BPMN.ScriptTask') {
1080
+ for (const row of payload.output ?? [])
1081
+ if (row.var)
1082
+ candidates.add(row.var);
1083
+ }
1084
+ if (el.$type === 'bpmn:SubProcess')
1085
+ walk(el);
1086
+ }
1087
+ };
1088
+ walk(process);
1089
+ // Keep only the candidates whose declaration matches what `implicitNodeVars` would
1090
+ // write: `{ id, name: id, type: 'object', direction: 'inputOutput' }`, i.e. an
1091
+ // `inputOutput` row, `name` equal to `id`, wire type `json`, and no default or
1092
+ // element scope. A declaration that differs in ANY of those cannot have been
1093
+ // derived, so it is the author's and has to be echoed.
1094
+ const out = new Set();
1095
+ const vars = (process.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
1096
+ for (const v of vars?.inputOutput ?? []) {
1097
+ if (!candidates.has(v.id))
1098
+ continue;
1099
+ if (v.name !== undefined && v.name !== v.id)
1100
+ continue;
1101
+ if (v.type !== 'json')
1102
+ continue;
1103
+ if (v.default !== undefined || v.elementId !== undefined)
1104
+ continue;
1105
+ out.add(v.id);
1106
+ }
1107
+ return out;
1108
+ }