@servicenow/sdk-build-plugins 4.2.0 → 4.4.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 (349) hide show
  1. package/dist/acl-plugin.js +11 -0
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.d.ts +2 -0
  4. package/dist/applicability-plugin.js +72 -0
  5. package/dist/applicability-plugin.js.map +1 -0
  6. package/dist/atf/test-plugin.js +5 -2
  7. package/dist/atf/test-plugin.js.map +1 -1
  8. package/dist/basic-syntax-plugin.js +7 -1
  9. package/dist/basic-syntax-plugin.js.map +1 -1
  10. package/dist/business-rule-plugin.js +1 -0
  11. package/dist/business-rule-plugin.js.map +1 -1
  12. package/dist/call-expression-plugin.js +1 -107
  13. package/dist/call-expression-plugin.js.map +1 -1
  14. package/dist/column/column-to-record.d.ts +10 -3
  15. package/dist/column/column-to-record.js +44 -7
  16. package/dist/column/column-to-record.js.map +1 -1
  17. package/dist/column-plugin.d.ts +3 -1
  18. package/dist/column-plugin.js +12 -12
  19. package/dist/column-plugin.js.map +1 -1
  20. package/dist/dashboard/dashboard-component-property-defaults.d.ts +152 -0
  21. package/dist/dashboard/dashboard-component-property-defaults.js +264 -0
  22. package/dist/dashboard/dashboard-component-property-defaults.js.map +1 -0
  23. package/dist/dashboard/dashboard-component-resolver.d.ts +13 -0
  24. package/dist/dashboard/dashboard-component-resolver.js +69 -0
  25. package/dist/dashboard/dashboard-component-resolver.js.map +1 -0
  26. package/dist/dashboard/dashboard-plugin.d.ts +12 -0
  27. package/dist/dashboard/dashboard-plugin.js +397 -0
  28. package/dist/dashboard/dashboard-plugin.js.map +1 -0
  29. package/dist/data-plugin.d.ts +3 -0
  30. package/dist/data-plugin.js +61 -113
  31. package/dist/data-plugin.js.map +1 -1
  32. package/dist/email-notification-plugin.d.ts +2 -0
  33. package/dist/email-notification-plugin.js +541 -0
  34. package/dist/email-notification-plugin.js.map +1 -0
  35. package/dist/flow/constants/flow-plugin-constants.d.ts +58 -0
  36. package/dist/flow/constants/flow-plugin-constants.js +70 -0
  37. package/dist/flow/constants/flow-plugin-constants.js.map +1 -0
  38. package/dist/flow/flow-logic/flow-logic-constants.d.ts +38 -0
  39. package/dist/flow/flow-logic/flow-logic-constants.js +118 -0
  40. package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -0
  41. package/dist/flow/flow-logic/flow-logic-diagnostics.d.ts +19 -0
  42. package/dist/flow/flow-logic/flow-logic-diagnostics.js +503 -0
  43. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -0
  44. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +62 -0
  45. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +2092 -0
  46. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -0
  47. package/dist/flow/flow-logic/flow-logic-plugin.d.ts +52 -0
  48. package/dist/flow/flow-logic/flow-logic-plugin.js +283 -0
  49. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -0
  50. package/dist/flow/flow-logic/flow-logic-shapes.d.ts +104 -0
  51. package/dist/flow/flow-logic/flow-logic-shapes.js +201 -0
  52. package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -0
  53. package/dist/flow/plugins/approval-rules-plugin.d.ts +2 -0
  54. package/dist/flow/plugins/approval-rules-plugin.js +49 -0
  55. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -0
  56. package/dist/flow/plugins/flow-action-definition-plugin.d.ts +2 -0
  57. package/dist/flow/plugins/flow-action-definition-plugin.js +286 -0
  58. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -0
  59. package/dist/flow/plugins/flow-data-pill-plugin.d.ts +9 -0
  60. package/dist/flow/plugins/flow-data-pill-plugin.js +212 -0
  61. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -0
  62. package/dist/flow/plugins/flow-definition-plugin.d.ts +2 -0
  63. package/dist/flow/plugins/flow-definition-plugin.js +1668 -0
  64. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -0
  65. package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +26 -0
  66. package/dist/flow/plugins/flow-diagnostics-plugin.js +217 -0
  67. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -0
  68. package/dist/flow/plugins/flow-instance-plugin.d.ts +12 -0
  69. package/dist/flow/plugins/flow-instance-plugin.js +1205 -0
  70. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -0
  71. package/dist/flow/plugins/flow-trigger-instance-plugin.d.ts +2 -0
  72. package/dist/flow/plugins/flow-trigger-instance-plugin.js +338 -0
  73. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -0
  74. package/dist/flow/plugins/inline-script-plugin.d.ts +39 -0
  75. package/dist/flow/plugins/inline-script-plugin.js +80 -0
  76. package/dist/flow/plugins/inline-script-plugin.js.map +1 -0
  77. package/dist/flow/plugins/step-definition-plugin.d.ts +5 -0
  78. package/dist/flow/plugins/step-definition-plugin.js +71 -0
  79. package/dist/flow/plugins/step-definition-plugin.js.map +1 -0
  80. package/dist/flow/plugins/step-instance-plugin.d.ts +31 -0
  81. package/dist/flow/plugins/step-instance-plugin.js +339 -0
  82. package/dist/flow/plugins/step-instance-plugin.js.map +1 -0
  83. package/dist/flow/plugins/trigger-plugin.d.ts +2 -0
  84. package/dist/flow/plugins/trigger-plugin.js +96 -0
  85. package/dist/flow/plugins/trigger-plugin.js.map +1 -0
  86. package/dist/flow/plugins/wfa-datapill-plugin.d.ts +15 -0
  87. package/dist/flow/plugins/wfa-datapill-plugin.js +178 -0
  88. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -0
  89. package/dist/flow/utils/approval-rules-processor.d.ts +13 -0
  90. package/dist/flow/utils/approval-rules-processor.js +267 -0
  91. package/dist/flow/utils/approval-rules-processor.js.map +1 -0
  92. package/dist/flow/utils/built-in-complex-objects.d.ts +19 -0
  93. package/dist/flow/utils/built-in-complex-objects.js +62 -0
  94. package/dist/flow/utils/built-in-complex-objects.js.map +1 -0
  95. package/dist/flow/utils/complex-object-resolver.d.ts +8 -0
  96. package/dist/flow/utils/complex-object-resolver.js +614 -0
  97. package/dist/flow/utils/complex-object-resolver.js.map +1 -0
  98. package/dist/flow/utils/complex-objects.d.ts +36 -0
  99. package/dist/flow/utils/complex-objects.js +481 -0
  100. package/dist/flow/utils/complex-objects.js.map +1 -0
  101. package/dist/flow/utils/data-pill-shapes.d.ts +58 -0
  102. package/dist/flow/utils/data-pill-shapes.js +135 -0
  103. package/dist/flow/utils/data-pill-shapes.js.map +1 -0
  104. package/dist/flow/utils/datapill-transformer.d.ts +110 -0
  105. package/dist/flow/utils/datapill-transformer.js +503 -0
  106. package/dist/flow/utils/datapill-transformer.js.map +1 -0
  107. package/dist/flow/utils/flow-constants.d.ts +72 -0
  108. package/dist/flow/utils/flow-constants.js +230 -0
  109. package/dist/flow/utils/flow-constants.js.map +1 -0
  110. package/dist/flow/utils/flow-io-to-record.d.ts +44 -0
  111. package/dist/flow/utils/flow-io-to-record.js +409 -0
  112. package/dist/flow/utils/flow-io-to-record.js.map +1 -0
  113. package/dist/flow/utils/flow-shapes.d.ts +161 -0
  114. package/dist/flow/utils/flow-shapes.js +255 -0
  115. package/dist/flow/utils/flow-shapes.js.map +1 -0
  116. package/dist/flow/utils/flow-to-xml.d.ts +16 -0
  117. package/dist/flow/utils/flow-to-xml.js +237 -0
  118. package/dist/flow/utils/flow-to-xml.js.map +1 -0
  119. package/dist/flow/utils/flow-variable-processor.d.ts +51 -0
  120. package/dist/flow/utils/flow-variable-processor.js +69 -0
  121. package/dist/flow/utils/flow-variable-processor.js.map +1 -0
  122. package/dist/flow/utils/label-cache-parser.d.ts +7 -0
  123. package/dist/flow/utils/label-cache-parser.js +24 -0
  124. package/dist/flow/utils/label-cache-parser.js.map +1 -0
  125. package/dist/flow/utils/label-cache-processor.d.ts +119 -0
  126. package/dist/flow/utils/label-cache-processor.js +719 -0
  127. package/dist/flow/utils/label-cache-processor.js.map +1 -0
  128. package/dist/flow/utils/pill-string-parser.d.ts +88 -0
  129. package/dist/flow/utils/pill-string-parser.js +306 -0
  130. package/dist/flow/utils/pill-string-parser.js.map +1 -0
  131. package/dist/flow/utils/schema-to-flow-object.d.ts +22 -0
  132. package/dist/flow/utils/schema-to-flow-object.js +318 -0
  133. package/dist/flow/utils/schema-to-flow-object.js.map +1 -0
  134. package/dist/flow/utils/service-catalog.d.ts +47 -0
  135. package/dist/flow/utils/service-catalog.js +137 -0
  136. package/dist/flow/utils/service-catalog.js.map +1 -0
  137. package/dist/flow/utils/utils.d.ts +117 -0
  138. package/dist/flow/utils/utils.js +345 -0
  139. package/dist/flow/utils/utils.js.map +1 -0
  140. package/dist/index.d.ts +20 -1
  141. package/dist/index.js +21 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/list-plugin.js +1 -1
  144. package/dist/list-plugin.js.map +1 -1
  145. package/dist/now-attach-plugin.d.ts +1 -0
  146. package/dist/now-attach-plugin.js +10 -10
  147. package/dist/now-attach-plugin.js.map +1 -1
  148. package/dist/now-ref-plugin.js +1 -1
  149. package/dist/now-ref-plugin.js.map +1 -1
  150. package/dist/record-plugin.d.ts +29 -0
  151. package/dist/record-plugin.js +66 -7
  152. package/dist/record-plugin.js.map +1 -1
  153. package/dist/repack/index.d.ts +2 -0
  154. package/dist/repack/index.js +8 -0
  155. package/dist/repack/index.js.map +1 -1
  156. package/dist/rest-api-plugin.js +54 -44
  157. package/dist/rest-api-plugin.js.map +1 -1
  158. package/dist/server-module-plugin/index.d.ts +10 -0
  159. package/dist/server-module-plugin/index.js +83 -59
  160. package/dist/server-module-plugin/index.js.map +1 -1
  161. package/dist/service-catalog/catalog-clientscript-plugin.d.ts +2 -0
  162. package/dist/service-catalog/catalog-clientscript-plugin.js +117 -0
  163. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -0
  164. package/dist/service-catalog/catalog-item-plugin.d.ts +2 -0
  165. package/dist/service-catalog/catalog-item-plugin.js +115 -0
  166. package/dist/service-catalog/catalog-item-plugin.js.map +1 -0
  167. package/dist/service-catalog/catalog-ui-policy-plugin.d.ts +2 -0
  168. package/dist/service-catalog/catalog-ui-policy-plugin.js +266 -0
  169. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -0
  170. package/dist/service-catalog/index.d.ts +5 -0
  171. package/dist/service-catalog/index.js +22 -0
  172. package/dist/service-catalog/index.js.map +1 -0
  173. package/dist/service-catalog/record-to-shape.d.ts +6 -0
  174. package/dist/service-catalog/record-to-shape.js +93 -0
  175. package/dist/service-catalog/record-to-shape.js.map +1 -0
  176. package/dist/service-catalog/sc-record-producer-plugin.d.ts +2 -0
  177. package/dist/service-catalog/sc-record-producer-plugin.js +140 -0
  178. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -0
  179. package/dist/service-catalog/service-catalog-base.d.ts +311 -0
  180. package/dist/service-catalog/service-catalog-base.js +542 -0
  181. package/dist/service-catalog/service-catalog-base.js.map +1 -0
  182. package/dist/service-catalog/service-catalog-diagnostics.d.ts +45 -0
  183. package/dist/service-catalog/service-catalog-diagnostics.js +172 -0
  184. package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -0
  185. package/dist/service-catalog/shape-to-record.d.ts +8 -0
  186. package/dist/service-catalog/shape-to-record.js +235 -0
  187. package/dist/service-catalog/shape-to-record.js.map +1 -0
  188. package/dist/service-catalog/utils.d.ts +323 -0
  189. package/dist/service-catalog/utils.js +1216 -0
  190. package/dist/service-catalog/utils.js.map +1 -0
  191. package/dist/service-catalog/variable-helper.d.ts +43 -0
  192. package/dist/service-catalog/variable-helper.js +92 -0
  193. package/dist/service-catalog/variable-helper.js.map +1 -0
  194. package/dist/service-catalog/variable-set-plugin.d.ts +2 -0
  195. package/dist/service-catalog/variable-set-plugin.js +175 -0
  196. package/dist/service-catalog/variable-set-plugin.js.map +1 -0
  197. package/dist/service-catalog/variables-transform.d.ts +139 -0
  198. package/dist/service-catalog/variables-transform.js +403 -0
  199. package/dist/service-catalog/variables-transform.js.map +1 -0
  200. package/dist/sla/sla-validators.d.ts +61 -0
  201. package/dist/sla/sla-validators.js +224 -0
  202. package/dist/sla/sla-validators.js.map +1 -0
  203. package/dist/sla-plugin.d.ts +5 -0
  204. package/dist/sla-plugin.js +280 -0
  205. package/dist/sla-plugin.js.map +1 -0
  206. package/dist/static-content-plugin.js +25 -2
  207. package/dist/static-content-plugin.js.map +1 -1
  208. package/dist/table-plugin.js +32 -15
  209. package/dist/table-plugin.js.map +1 -1
  210. package/dist/ui-page-plugin.js +832 -19
  211. package/dist/ui-page-plugin.js.map +1 -1
  212. package/dist/ui-policy-plugin.js +5 -7
  213. package/dist/ui-policy-plugin.js.map +1 -1
  214. package/dist/utils.d.ts +10 -1
  215. package/dist/utils.js +16 -0
  216. package/dist/utils.js.map +1 -1
  217. package/dist/ux-list-menu-config-plugin.d.ts +2 -0
  218. package/dist/ux-list-menu-config-plugin.js +292 -0
  219. package/dist/ux-list-menu-config-plugin.js.map +1 -0
  220. package/dist/workspace-plugin/chrome-tab.d.ts +2 -0
  221. package/dist/workspace-plugin/chrome-tab.js +46 -0
  222. package/dist/workspace-plugin/chrome-tab.js.map +1 -0
  223. package/dist/workspace-plugin/constants.d.ts +52 -0
  224. package/dist/workspace-plugin/constants.js +56 -0
  225. package/dist/workspace-plugin/constants.js.map +1 -0
  226. package/dist/workspace-plugin/fluent-utils.d.ts +9 -0
  227. package/dist/workspace-plugin/fluent-utils.js +60 -0
  228. package/dist/workspace-plugin/fluent-utils.js.map +1 -0
  229. package/dist/workspace-plugin/page.d.ts +8 -0
  230. package/dist/workspace-plugin/page.js +108 -0
  231. package/dist/workspace-plugin/page.js.map +1 -0
  232. package/dist/workspace-plugin/screen.d.ts +1 -0
  233. package/dist/workspace-plugin/screen.js +38 -0
  234. package/dist/workspace-plugin/screen.js.map +1 -0
  235. package/dist/workspace-plugin/templates/index.d.ts +10 -0
  236. package/dist/workspace-plugin/templates/index.js +20 -0
  237. package/dist/workspace-plugin/templates/index.js.map +1 -0
  238. package/dist/workspace-plugin/templates/record-page-composition.d.ts +1 -0
  239. package/dist/workspace-plugin/templates/record-page-composition.js +4043 -0
  240. package/dist/workspace-plugin/templates/record-page-composition.js.map +1 -0
  241. package/dist/workspace-plugin/templates/record-page-data.d.ts +1 -0
  242. package/dist/workspace-plugin/templates/record-page-data.js +527 -0
  243. package/dist/workspace-plugin/templates/record-page-data.js.map +1 -0
  244. package/dist/workspace-plugin/templates/record-page-interalEventMappings.d.ts +1 -0
  245. package/dist/workspace-plugin/templates/record-page-interalEventMappings.js +39 -0
  246. package/dist/workspace-plugin/templates/record-page-interalEventMappings.js.map +1 -0
  247. package/dist/workspace-plugin/templates/record-page-layoutModel.d.ts +1 -0
  248. package/dist/workspace-plugin/templates/record-page-layoutModel.js +55 -0
  249. package/dist/workspace-plugin/templates/record-page-layoutModel.js.map +1 -0
  250. package/dist/workspace-plugin/templates/record-page-properties.d.ts +1 -0
  251. package/dist/workspace-plugin/templates/record-page-properties.js +135 -0
  252. package/dist/workspace-plugin/templates/record-page-properties.js.map +1 -0
  253. package/dist/workspace-plugin/templates/record-page.d.ts +3 -0
  254. package/dist/workspace-plugin/templates/record-page.js +8 -0
  255. package/dist/workspace-plugin/templates/record-page.js.map +1 -0
  256. package/dist/workspace-plugin.d.ts +2 -0
  257. package/dist/workspace-plugin.js +453 -0
  258. package/dist/workspace-plugin.js.map +1 -0
  259. package/package.json +10 -12
  260. package/src/acl-plugin.ts +16 -1
  261. package/src/applicability-plugin.ts +82 -0
  262. package/src/atf/test-plugin.ts +6 -3
  263. package/src/basic-syntax-plugin.ts +10 -1
  264. package/src/business-rule-plugin.ts +2 -1
  265. package/src/call-expression-plugin.ts +2 -130
  266. package/src/column/column-to-record.ts +54 -8
  267. package/src/column-plugin.ts +29 -13
  268. package/src/dashboard/dashboard-component-property-defaults.ts +277 -0
  269. package/src/dashboard/dashboard-component-resolver.ts +69 -0
  270. package/src/dashboard/dashboard-plugin.ts +450 -0
  271. package/src/data-plugin.ts +67 -139
  272. package/src/email-notification-plugin.ts +850 -0
  273. package/src/flow/constants/flow-plugin-constants.ts +79 -0
  274. package/src/flow/flow-logic/flow-logic-constants.ts +120 -0
  275. package/src/flow/flow-logic/flow-logic-diagnostics.ts +591 -0
  276. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +2550 -0
  277. package/src/flow/flow-logic/flow-logic-plugin.ts +337 -0
  278. package/src/flow/flow-logic/flow-logic-shapes.ts +215 -0
  279. package/src/flow/plugins/approval-rules-plugin.ts +48 -0
  280. package/src/flow/plugins/flow-action-definition-plugin.ts +295 -0
  281. package/src/flow/plugins/flow-data-pill-plugin.ts +258 -0
  282. package/src/flow/plugins/flow-definition-plugin.ts +2173 -0
  283. package/src/flow/plugins/flow-diagnostics-plugin.ts +280 -0
  284. package/src/flow/plugins/flow-instance-plugin.ts +1499 -0
  285. package/src/flow/plugins/flow-trigger-instance-plugin.ts +444 -0
  286. package/src/flow/plugins/inline-script-plugin.ts +83 -0
  287. package/src/flow/plugins/step-definition-plugin.ts +67 -0
  288. package/src/flow/plugins/step-instance-plugin.ts +431 -0
  289. package/src/flow/plugins/trigger-plugin.ts +95 -0
  290. package/src/flow/plugins/wfa-datapill-plugin.ts +213 -0
  291. package/src/flow/utils/approval-rules-processor.ts +298 -0
  292. package/src/flow/utils/built-in-complex-objects.ts +81 -0
  293. package/src/flow/utils/complex-object-resolver.ts +875 -0
  294. package/src/flow/utils/complex-objects.ts +656 -0
  295. package/src/flow/utils/data-pill-shapes.ts +165 -0
  296. package/src/flow/utils/datapill-transformer.ts +632 -0
  297. package/src/flow/utils/flow-constants.ts +285 -0
  298. package/src/flow/utils/flow-io-to-record.ts +533 -0
  299. package/src/flow/utils/flow-shapes.ts +296 -0
  300. package/src/flow/utils/flow-to-xml.ts +318 -0
  301. package/src/flow/utils/flow-variable-processor.ts +100 -0
  302. package/src/flow/utils/label-cache-parser.ts +37 -0
  303. package/src/flow/utils/label-cache-processor.ts +870 -0
  304. package/src/flow/utils/pill-string-parser.ts +375 -0
  305. package/src/flow/utils/schema-to-flow-object.ts +385 -0
  306. package/src/flow/utils/service-catalog.ts +174 -0
  307. package/src/flow/utils/utils.ts +395 -0
  308. package/src/index.ts +20 -1
  309. package/src/list-plugin.ts +1 -1
  310. package/src/now-attach-plugin.ts +14 -11
  311. package/src/now-ref-plugin.ts +1 -1
  312. package/src/record-plugin.ts +76 -11
  313. package/src/repack/index.ts +14 -0
  314. package/src/rest-api-plugin.ts +62 -50
  315. package/src/server-module-plugin/index.ts +112 -86
  316. package/src/service-catalog/catalog-clientscript-plugin.ts +140 -0
  317. package/src/service-catalog/catalog-item-plugin.ts +162 -0
  318. package/src/service-catalog/catalog-ui-policy-plugin.ts +324 -0
  319. package/src/service-catalog/index.ts +5 -0
  320. package/src/service-catalog/record-to-shape.ts +109 -0
  321. package/src/service-catalog/sc-record-producer-plugin.ts +201 -0
  322. package/src/service-catalog/service-catalog-base.ts +600 -0
  323. package/src/service-catalog/service-catalog-diagnostics.ts +254 -0
  324. package/src/service-catalog/shape-to-record.ts +279 -0
  325. package/src/service-catalog/utils.ts +1455 -0
  326. package/src/service-catalog/variable-helper.ts +135 -0
  327. package/src/service-catalog/variable-set-plugin.ts +197 -0
  328. package/src/service-catalog/variables-transform.ts +438 -0
  329. package/src/sla/sla-validators.ts +331 -0
  330. package/src/sla-plugin.ts +358 -0
  331. package/src/static-content-plugin.ts +25 -2
  332. package/src/table-plugin.ts +49 -16
  333. package/src/ui-page-plugin.ts +1063 -20
  334. package/src/ui-policy-plugin.ts +5 -9
  335. package/src/utils.ts +24 -1
  336. package/src/ux-list-menu-config-plugin.ts +312 -0
  337. package/src/workspace-plugin/chrome-tab.ts +44 -0
  338. package/src/workspace-plugin/constants.ts +53 -0
  339. package/src/workspace-plugin/fluent-utils.ts +60 -0
  340. package/src/workspace-plugin/page.ts +139 -0
  341. package/src/workspace-plugin/screen.ts +34 -0
  342. package/src/workspace-plugin/templates/index.ts +17 -0
  343. package/src/workspace-plugin/templates/record-page-composition.ts +4051 -0
  344. package/src/workspace-plugin/templates/record-page-data.ts +523 -0
  345. package/src/workspace-plugin/templates/record-page-interalEventMappings.ts +35 -0
  346. package/src/workspace-plugin/templates/record-page-layoutModel.ts +51 -0
  347. package/src/workspace-plugin/templates/record-page-properties.ts +131 -0
  348. package/src/workspace-plugin/templates/record-page.ts +6 -0
  349. package/src/workspace-plugin.ts +574 -0
@@ -0,0 +1,2550 @@
1
+ import type { Factory, Diagnostics, Result, Transform, Database, StringShape } from '@servicenow/sdk-build-core'
2
+ import type {
3
+ FlowLogicInstanceShape,
4
+ ConditionalLogicInstanceShape,
5
+ ForEachInstanceShape,
6
+ GoBackToInstanceShape,
7
+ TryCatchInstanceShape,
8
+ DoInParallelInstanceShape,
9
+ SetFlowVariablesInstanceShape,
10
+ AssignSubflowOutputsInstanceShape,
11
+ } from './flow-logic-shapes'
12
+ import {
13
+ ts,
14
+ IdentifierShape,
15
+ ObjectShape,
16
+ ArrayShape,
17
+ Shape,
18
+ Record,
19
+ PropertyAccessShape,
20
+ TemplateExpressionShape,
21
+ CallExpressionShape,
22
+ DurationShape,
23
+ } from '@servicenow/sdk-build-core'
24
+ import { PillShape, PillTemplateShape } from '../utils/data-pill-shapes'
25
+ import { gunzipSync } from 'zlib'
26
+ import { normalizeInputValue } from '../plugins/flow-instance-plugin'
27
+ import { FDInlineScriptCallShape } from '../plugins/inline-script-plugin'
28
+ import { InlineScriptShape } from '../utils/flow-shapes'
29
+ import {
30
+ FlowLogicSysId,
31
+ FLOW_LOGIC,
32
+ NAME_MAP,
33
+ DURATION_TYPE_PROPS,
34
+ WAIT_FOR_A_DURATION_INPUT_DESCRIPTORS,
35
+ } from './flow-logic-constants'
36
+ import { resolveDataPillShape, sysIdToUuid } from '../utils/utils'
37
+ import { ArrowFunctionShape } from '../../arrow-function-plugin'
38
+ import { NowIdShape } from '../../now-id-plugin'
39
+ import { validateFlowLogic, validateSibling } from './flow-logic-diagnostics'
40
+ import { determineRootIdentifierAndPath } from '../plugins/flow-data-pill-plugin'
41
+ import type { Duration } from '@servicenow/sdk-core/runtime/db'
42
+
43
+ async function buildFlowLogicInstanceRecord(
44
+ expr: FlowLogicInstanceShape,
45
+ factory: Factory,
46
+ values: FlowLogicValues
47
+ ): Promise<Record> {
48
+ const partialRecord = await factory.createRecord({
49
+ source: expr,
50
+ table: 'sys_hub_flow_logic_instance_v2',
51
+ explicitId: expr.getSysId(),
52
+ properties: {
53
+ comment: expr.getAnnotation()?.getValue() ?? '',
54
+ logic_definition: FlowLogicSysId.getLogicId(expr.getCallee() as FLOW_LOGIC),
55
+ values: values,
56
+ },
57
+ })
58
+ return partialRecord.merge({ ui_id: sysIdToUuid(partialRecord.getId().getValue()) })
59
+ }
60
+
61
+ interface FlowLogicValueInput {
62
+ name: string
63
+ value: string
64
+ displayValue?: string
65
+ children?: unknown[]
66
+ parameter?: { [key: string]: unknown }
67
+ }
68
+
69
+ interface FlowLogicValueWithScript extends FlowLogicValueInput {
70
+ scriptActive?: boolean
71
+ }
72
+
73
+ interface AssignSubflowOutput extends FlowLogicValueWithScript {}
74
+
75
+ interface SetFlowVariablesInput extends FlowLogicValueWithScript {}
76
+
77
+ interface WaitForADurationInput extends FlowLogicValueInput {
78
+ id: string
79
+ scriptActive: boolean
80
+ }
81
+
82
+ export interface FlowLogicValues {
83
+ inputs: FlowLogicValueInput[]
84
+ outputsToAssign: AssignSubflowOutput[]
85
+ variables: SetFlowVariablesInput[]
86
+ decisionTableInputs: []
87
+ dynamicInputs: []
88
+ workflowInputs: []
89
+ }
90
+
91
+ /**
92
+ * Creates an empty FlowLogicValues object with all fields initialized to empty arrays
93
+ * @returns Empty FlowLogicValues structure
94
+ */
95
+ export function createEmptyFlowLogicValues(): FlowLogicValues {
96
+ return {
97
+ inputs: [],
98
+ outputsToAssign: [],
99
+ variables: [],
100
+ decisionTableInputs: [],
101
+ dynamicInputs: [],
102
+ workflowInputs: [],
103
+ }
104
+ }
105
+
106
+ type FlowLogicParseResult =
107
+ | { condition?: string; label?: string } // If/ElseIf/Else
108
+ | string // GoBackTo step ID
109
+ | { [key: string]: unknown } // Generic for future types
110
+
111
+ /**
112
+ * FlowLogicValueProcessor handles bidirectional conversion between Fluent TypeScript
113
+ * flow logic calls and ServiceNow XML flow records.
114
+ *
115
+ * ## Architecture
116
+ *
117
+ * **Forward Flow (Fluent → XML):**
118
+ * - `prepare()` converts TypeScript flow logic calls to ServiceNow XML format
119
+ * - Extracts configuration properties and creates structured input data
120
+ * - Data gets gzipped and stored in sys_hub_flow_logic_instance_v2 records
121
+ *
122
+ * **Reverse Flow (XML → Fluent):**
123
+ * - `parse()` converts ServiceNow XML records back to TypeScript
124
+ * - Unzips and parses stored JSON data
125
+ * - Reconstructs original configuration objects for shape generation
126
+ *
127
+ * ## Data Structure
128
+ *
129
+ * All flow logic data is stored as FlowLogicValues containing an array of
130
+ * FlowLogicValueInput objects with name/value pairs that represent the
131
+ * original TypeScript configuration properties.
132
+ */
133
+ namespace FlowLogicValueProcessor {
134
+ /**
135
+ * Generic factory function for creating flow logic entry objects.
136
+ * All entry types (AssignSubflowOutput, SetFlowVariablesInput, FlowLogicValueInput)
137
+ * share the same structure, differing only in displayValue and type name.
138
+ *
139
+ * @param name - Entry name
140
+ * @param value - Entry value (may contain datapills like "{{trigger.name}}")
141
+ * @param children - Optional children array for complex objects (FlowObject/FlowArray)
142
+ * @param displayValue - Display value shown in ServiceNow UI (defaults to value)
143
+ * @returns Entry object with the specified type
144
+ */
145
+ function createEntry<T extends SetFlowVariablesInput | AssignSubflowOutput | FlowLogicValueInput>(
146
+ name: string,
147
+ value: string,
148
+ children?: unknown[],
149
+ displayValue: string = value
150
+ ): T {
151
+ return {
152
+ name,
153
+ value,
154
+ displayValue,
155
+ children: children || [],
156
+ parameter: {},
157
+ scriptActive: false,
158
+ } as T
159
+ }
160
+
161
+ function createInput(name: string, value: string): FlowLogicValueInput {
162
+ return createEntry<FlowLogicValueInput>(name, value)
163
+ }
164
+
165
+ /**
166
+ * Creates an AssignSubflowOutput entry with full display value.
167
+ * Used when generating XML from Fluent code for assignSubflowOutputs.
168
+ *
169
+ * @param name - Output name
170
+ * @param value - Output value (may contain datapills like "{{trigger.name}}")
171
+ * @param children - Optional children array for complex objects (FlowObject/FlowArray)
172
+ * @returns AssignSubflowOutput with displayValue set to the value
173
+ */
174
+ function createOutput(name: string, value: string, children?: unknown[]): AssignSubflowOutput {
175
+ return createEntry<AssignSubflowOutput>(name, value, children)
176
+ }
177
+
178
+ /**
179
+ * Creates a SetFlowVariablesInput entry for the 'variables' array with full display value.
180
+ * Used when generating XML from Fluent code.
181
+ *
182
+ * @param name - Variable name
183
+ * @param value - Variable value (may contain datapills like "{{trigger.name}}")
184
+ * @param children - Optional children array for complex objects (FlowObject/FlowArray)
185
+ * @returns SetFlowVariablesInput with displayValue set to the value
186
+ */
187
+ function createVariable(name: string, value: string, children?: unknown[]): SetFlowVariablesInput {
188
+ return createEntry<SetFlowVariablesInput>(name, value, children)
189
+ }
190
+
191
+ /**
192
+ * Creates a SetFlowVariablesInput entry for the 'inputs' array with empty display value.
193
+ * Used when generating XML from Fluent code for the inputs array (required for backward compatibility).
194
+ *
195
+ * @param name - Variable name
196
+ * @param value - Variable value
197
+ * @param children - Optional children array for complex objects (FlowObject/FlowArray)
198
+ * @returns SetFlowVariablesInput with empty displayValue
199
+ */
200
+ function createVariableInput(name: string, value: string, children?: unknown[]): SetFlowVariablesInput {
201
+ return createEntry<SetFlowVariablesInput>(name, value, children, '')
202
+ }
203
+
204
+ // Factory pattern for value preparation
205
+ type InputValuePreparer = (
206
+ data: ObjectShape | Record | StringShape,
207
+ args?: { [key: string]: unknown }
208
+ ) => FlowLogicValueInput[]
209
+
210
+ const INPUT_VALUE_PREPARERS: { [K in FLOW_LOGIC]: InputValuePreparer } = {
211
+ [FLOW_LOGIC.ELSE]: () => [],
212
+ [FLOW_LOGIC.GOBACKTO]: (data: Shape) => prepareGoBackTo(data.isString() ? data.asString() : data.asRecord()),
213
+ [FLOW_LOGIC.IF]: (data) => prepareIfElse(data.asObject()),
214
+ [FLOW_LOGIC.ELSEIF]: (data) => prepareIfElse(data.asObject()),
215
+ [FLOW_LOGIC.WAIT_FOR_A_DURATION]: (data) => prepareWaitForADuration(data.asObject()),
216
+ [FLOW_LOGIC.EXITLOOP]: () => [],
217
+ [FLOW_LOGIC.ENDFLOW]: () => [],
218
+ [FLOW_LOGIC.SKIP_ITERATION]: () => [],
219
+ [FLOW_LOGIC.FOR_EACH]: (data) => prepareForEach(data.asString()),
220
+ [FLOW_LOGIC.DO_IN_PARALLEL]: () => [],
221
+ [FLOW_LOGIC.TRY_CATCH]: () => [],
222
+ [FLOW_LOGIC.SET_FLOW_VARIABLES]: (data) => {
223
+ return prepareSetFlowVariables(data.asObject(), '')
224
+ },
225
+ [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: (data) => {
226
+ return prepareAssignSubflowOutputs(data.asObject())
227
+ },
228
+ }
229
+
230
+ /**
231
+ * Converts Fluent TypeScript flow logic calls into ServiceNow XML format.
232
+ *
233
+ * **Process:**
234
+ * 1. Takes the flow logic type (e.g., 'IF') and configuration data
235
+ * 2. Uses the appropriate VALUE_PREPARER function
236
+ * 3. Extracts relevant properties (condition, label, etc.)
237
+ * 4. Creates FlowLogicValueInput objects with name/value pairs
238
+ * 5. Returns FlowLogicValues structure that gets gzipped into XML
239
+ *
240
+ * **Example:**
241
+ * ```typescript
242
+ * // Input: If({ condition: "user.role === 'admin'", label: "Admin Check" })
243
+ * // Output: { inputs: [
244
+ * // { name: 'condition_name', value: 'Admin Check' },
245
+ * // { name: 'condition', value: "user.role === 'admin'" }
246
+ * // ]}
247
+ * ```
248
+ *
249
+ * @param logicType - The flow logic type key (e.g., 'IF', 'ELSEIF', 'GOBACKTO')
250
+ * @param data - Configuration data from the Fluent TypeScript call
251
+ * @param args - Optional arguments including childNameMetadata for FlowArray fields
252
+ * @returns FlowLogicValues structure ready for XML serialization
253
+ */
254
+ export function prepare(
255
+ logicType: FLOW_LOGIC,
256
+ data: ObjectShape | Record | StringShape,
257
+ args?: { [key: string]: unknown; childNameMetadata?: Map<string, string> }
258
+ ): FlowLogicValues {
259
+ const preparer = INPUT_VALUE_PREPARERS[logicType]
260
+ if (!preparer) {
261
+ throw new Error(`Unsupported flow logic type: ${logicType}`)
262
+ }
263
+
264
+ const preparedData = preparer(data, args)
265
+
266
+ return {
267
+ outputsToAssign: logicType === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS ? preparedData : [],
268
+ variables:
269
+ logicType === FLOW_LOGIC.SET_FLOW_VARIABLES
270
+ ? prepareSetFlowVariables(data.asObject(), 'variables')
271
+ : [],
272
+ decisionTableInputs: [],
273
+ dynamicInputs: [],
274
+ workflowInputs: [],
275
+ inputs: logicType === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS ? [] : preparedData,
276
+ }
277
+ }
278
+
279
+ function prepareIfElse(config: ObjectShape): FlowLogicValueInput[] {
280
+ const inputs: FlowLogicValueInput[] = []
281
+ // After datapill resolution, values are already resolved strings, so use getValue() directly
282
+ const conditionValue = config.get('condition')?.getValue()
283
+ const conditionString = typeof conditionValue === 'string' ? conditionValue : String(conditionValue ?? '')
284
+ const labelValue = config.get('label')?.getValue()
285
+ const label = labelValue && typeof labelValue === 'string' ? labelValue : ''
286
+
287
+ if (!conditionString) {
288
+ return []
289
+ }
290
+
291
+ if (label) {
292
+ inputs.push(createInput('condition_name', label))
293
+ }
294
+
295
+ inputs.push(createInput('condition', conditionString))
296
+ return inputs
297
+ }
298
+
299
+ function prepareGoBackTo(step: Record | StringShape): FlowLogicValueInput[] {
300
+ const stepId = step.isRecord() ? step.get('ui_id').asString().getValue() : step.asString().getValue()
301
+ return [createInput('go_back_to_step', stepId)]
302
+ }
303
+
304
+ function prepareForEach(config: StringShape): FlowLogicValueInput[] {
305
+ // ForEach needs to extract the items array from the first argument
306
+ // The structure is: ForEach(items, config, body)
307
+ // For now, we create a basic items input descriptor based on the XML structure we found
308
+
309
+ const inputs: FlowLogicValueInput[] = []
310
+
311
+ // Create the items input descriptor based on the XML structure we found
312
+ const itemsInput: FlowLogicValueInput = {
313
+ name: 'items',
314
+ value: config.getValue(),
315
+ displayValue: config.getValue(),
316
+ }
317
+
318
+ inputs.push(itemsInput)
319
+ return inputs
320
+ }
321
+
322
+ function prepareWaitForADuration(config: ObjectShape): WaitForADurationInput[] {
323
+ const inputDescriptors = JSON.parse(JSON.stringify(WAIT_FOR_A_DURATION_INPUT_DESCRIPTORS))
324
+
325
+ // Helper to safely get config value as string (after datapill resolution, values are already resolved)
326
+ const getConfigValue = (key: string): string => {
327
+ const value = config.get(key)?.getValue()
328
+ return value !== undefined && value !== null ? String(value) : ''
329
+ }
330
+
331
+ // Map the instanceProps to the correct fields in the template
332
+ // durationType: explicit_duration | relative_duration | percentage_duration
333
+ const durationType = getConfigValue('durationType')
334
+
335
+ // Duration field is optional (not required for percentage_duration)
336
+ const durationField = config.get('duration')
337
+ const duration =
338
+ durationField && !durationField.isUndefined()
339
+ ? new DurationShape({
340
+ source: config.getOriginalNode(),
341
+ value: durationField.getValue() as Duration,
342
+ })
343
+ : durationField
344
+
345
+ const schedule = getConfigValue('schedule')
346
+ const durationTypeDisplayMap: { [key: string]: string } = {
347
+ explicit_duration: 'Explicit duration',
348
+ relative_duration: 'Relative duration',
349
+ percentage_duration: 'Percentage duration',
350
+ }
351
+
352
+ const updateDescriptor = (index: number, value: string, displayValue?: string, parameterType?: string) => {
353
+ if (inputDescriptors[index]) {
354
+ inputDescriptors[index].value = value
355
+ inputDescriptors[index].displayValue = displayValue ?? value
356
+ // Add parameter.type to preserve type information for round-trip transformation
357
+ if (parameterType) {
358
+ inputDescriptors[index].parameter = { type: parameterType }
359
+ }
360
+ }
361
+ }
362
+
363
+ updateDescriptor(0, durationType, durationTypeDisplayMap[durationType])
364
+ const durationValue = duration?.toString()?.getValue() || ''
365
+ // Include 'glide_duration' type so XML → Fluent transform knows to create DurationShape
366
+ updateDescriptor(3, durationValue, durationValue, 'glide_duration')
367
+ updateDescriptor(6, schedule)
368
+
369
+ if (durationType === 'relative_duration') {
370
+ updateDescriptor(4, getConfigValue('relativeOperator'))
371
+ updateDescriptor(5, getConfigValue('relativeDatetime'))
372
+ }
373
+
374
+ if (durationType === 'percentage_duration') {
375
+ updateDescriptor(2, getConfigValue('percentage'))
376
+ updateDescriptor(1, getConfigValue('percentageDatetime'))
377
+ }
378
+
379
+ return inputDescriptors
380
+ }
381
+
382
+ /**
383
+ * Checks if a value is a serialized inline script object.
384
+ * Inline script objects have the shape: { scriptActive: true, script: {...}, name, value, ... }
385
+ *
386
+ * @param value - The value to check
387
+ * @returns true if value is an inline script object, false otherwise
388
+ */
389
+ function isSerializedInlineScript(value: unknown): boolean {
390
+ return (
391
+ typeof value === 'object' &&
392
+ value !== null &&
393
+ !Array.isArray(value) &&
394
+ (value as { scriptActive?: boolean }).scriptActive === true
395
+ )
396
+ }
397
+
398
+ /**
399
+ * Builds a flattened children array from a nested object or array.
400
+ * Used for the 'children' field in SetFlowVariablesInput for complex objects (FlowObject/FlowArray).
401
+ *
402
+ * @param value - The object or array value
403
+ * @param childName - Optional childName for FlowArray elements (e.g., 'tag', 'color')
404
+ * @returns Array of child entries with name, value, displayValue, and scriptActive fields
405
+ */
406
+ function buildChildrenArray(value: unknown, childName?: string): unknown[] {
407
+ const children: unknown[] = []
408
+
409
+ if (Array.isArray(value)) {
410
+ // FlowArray: Create a child entry for each array element
411
+ // Use childName if provided (matches UI behavior), otherwise empty string (backward compatibility)
412
+ const elementName = childName || ''
413
+
414
+ for (const element of value) {
415
+ if (typeof element === 'object' && element !== null) {
416
+ // Nested object in array
417
+ children.push({
418
+ name: elementName,
419
+ value: '',
420
+ displayValue: '',
421
+ children: buildChildrenArray(element),
422
+ scriptActive: false,
423
+ })
424
+ } else {
425
+ // Primitive in array
426
+ const elementStr =
427
+ element === null || element === undefined || element === '' ? '' : String(element)
428
+ children.push({
429
+ name: elementName,
430
+ value: elementStr,
431
+ displayValue: elementStr,
432
+ scriptActive: false,
433
+ })
434
+ }
435
+ }
436
+ } else if (typeof value === 'object' && value !== null) {
437
+ // FlowObject: Create a child entry for each field
438
+ for (const [fieldName, fieldValue] of Object.entries(value)) {
439
+ if (typeof fieldValue === 'object' && fieldValue !== null && !Array.isArray(fieldValue)) {
440
+ // Nested object
441
+ children.push({
442
+ name: fieldName,
443
+ value: '',
444
+ displayValue: '',
445
+ children: buildChildrenArray(fieldValue),
446
+ scriptActive: false,
447
+ })
448
+ } else if (Array.isArray(fieldValue)) {
449
+ // Nested array - Note: nested arrays don't have childName metadata yet
450
+ children.push({
451
+ name: fieldName,
452
+ value: '',
453
+ displayValue: '',
454
+ children: buildChildrenArray(fieldValue),
455
+ scriptActive: false,
456
+ })
457
+ } else {
458
+ // Primitive field
459
+ const fieldStr =
460
+ fieldValue === null || fieldValue === undefined || fieldValue === '' ? '' : String(fieldValue)
461
+ children.push({
462
+ name: fieldName,
463
+ value: fieldStr,
464
+ displayValue: fieldStr,
465
+ scriptActive: false,
466
+ })
467
+ }
468
+ }
469
+ }
470
+
471
+ return children
472
+ }
473
+
474
+ /**
475
+ * Generic helper to prepare entries from ObjectShape for XML serialization.
476
+ * Extracts key-value pairs and applies a factory function to create typed entries.
477
+ *
478
+ * Handles inline script objects (with scriptActive: true) by returning them as-is.
479
+ * Also handles complex objects (FlowObject/FlowArray) by building children arrays.
480
+ *
481
+ * @param values - ObjectShape containing name-value pairs from Fluent code
482
+ * @param factory - Factory function to create entry objects from key-value pairs
483
+ * @param childNameMetadata - Optional map of field name → childName for FlowArray fields
484
+ * @param useDottedNames - If true, use "fieldName.childName" format for array elements (assignSubflowOutputs)
485
+ * @returns Array of entries ready for XML serialization
486
+ */
487
+ function prepareEntries<T>(
488
+ values: ObjectShape,
489
+ factory: (key: string, value: string, children?: unknown[]) => T
490
+ ): T[] {
491
+ if (!values || values.isUndefined()) {
492
+ return []
493
+ }
494
+
495
+ const results: T[] = []
496
+ const entries = values.entries({ resolve: false })
497
+
498
+ for (const [key, shape] of entries) {
499
+ // Check if the shape is an ObjectShape (for FlowObject assignments)
500
+ if (shape.is(ObjectShape)) {
501
+ const serializedObj = serializeShapeToValue(shape)
502
+
503
+ // Check if the serialized value is an inline script (has scriptActive: true)
504
+ if (isSerializedInlineScript(serializedObj)) {
505
+ // Inline scripts are already in the correct format - return as-is
506
+ results.push(serializedObj as unknown as T)
507
+ continue
508
+ }
509
+
510
+ // Regular FlowObject - children with correct names will be added by resolveComplexInput in postProcess
511
+ const children = buildChildrenArray(serializedObj)
512
+ results.push(factory(key, JSON.stringify(serializedObj), children))
513
+ continue
514
+ }
515
+
516
+ // Check if the shape is an ArrayShape (for FlowArray assignments)
517
+ if (shape.is(ArrayShape)) {
518
+ const arrShape = shape.as(ArrayShape)
519
+ const serializedArr: unknown[] = []
520
+ for (const elem of arrShape.getElements()) {
521
+ serializedArr.push(serializeShapeToValue(elem))
522
+ }
523
+ // Children with correct names will be added by resolveComplexInput in postProcess
524
+ const children = buildChildrenArray(serializedArr)
525
+ results.push(factory(key, JSON.stringify(serializedArr), children))
526
+ continue
527
+ }
528
+
529
+ // For all other shapes, get their value
530
+ const value = shape.getValue()
531
+
532
+ // Check if value is a serialized inline script object
533
+ if (isSerializedInlineScript(value)) {
534
+ // Inline scripts are already in the correct format - return as-is
535
+ results.push(value as unknown as T)
536
+ continue
537
+ }
538
+
539
+ // Check if value is a plain object or array (shouldn't happen but just in case)
540
+ if (typeof value === 'object' && value !== null) {
541
+ const children = buildChildrenArray(value)
542
+ results.push(factory(key, JSON.stringify(value), children))
543
+ continue
544
+ }
545
+
546
+ // Primitive values (string, number, boolean, datapills)
547
+ results.push(factory(key, String(value)))
548
+ }
549
+
550
+ return results
551
+ }
552
+
553
+ /**
554
+ * Recursively serializes a Shape to a plain JavaScript value, preserving datapills as pill strings.
555
+ * @param shape - The shape to serialize
556
+ * @returns Plain JavaScript value with datapills as strings like "{{trigger.name}}"
557
+ */
558
+ function serializeShapeToValue(shape: Shape): unknown {
559
+ if (shape.is(ObjectShape)) {
560
+ const obj: { [key: string]: unknown } = {}
561
+ for (const [key, valueShape] of shape.as(ObjectShape).entries({ resolve: false })) {
562
+ obj[key] = serializeShapeToValue(valueShape)
563
+ }
564
+ return obj
565
+ }
566
+
567
+ if (shape.is(ArrayShape)) {
568
+ const arr: unknown[] = []
569
+ for (const elem of shape.as(ArrayShape).getElements()) {
570
+ arr.push(serializeShapeToValue(elem))
571
+ }
572
+ return arr
573
+ }
574
+
575
+ // Handle TemplateExpressionShape (template strings with datapills)
576
+ if (shape.is(TemplateExpressionShape)) {
577
+ // TemplateExpressionShape should have been converted to PillTemplateShape by DataPillPlugin
578
+ // If it's a PillTemplateShape, get its value
579
+ if (shape.is(PillTemplateShape)) {
580
+ return shape.as(PillTemplateShape).getValue()
581
+ }
582
+ // Otherwise, get the regular value
583
+ return shape.getValue()
584
+ }
585
+
586
+ // Handle PillShape (wfa.dataPill() is converted to PillShape by WfaDataPillPlugin)
587
+ if (shape.is(PillShape)) {
588
+ return shape.as(PillShape).getValue()
589
+ }
590
+
591
+ // For all other shapes (primitives, etc.), get the value
592
+ return shape.getValue()
593
+ }
594
+
595
+ /**
596
+ * Prepares SetFlowVariables data for XML serialization.
597
+ * Converts an ObjectShape containing variable assignments into an array of SetFlowVariablesInput entries.
598
+ *
599
+ * The `type` parameter determines which array format to use:
600
+ * - 'variables': Creates entries with full displayValue (for the 'variables' array in XML)
601
+ * - default: Creates entries with empty displayValue (for the 'inputs' array in XML)
602
+ *
603
+ * This dual behavior is required because ServiceNow stores SetFlowVariables data in both
604
+ * the 'variables' array and the 'inputs' array for backward compatibility.
605
+ *
606
+ * @param values - ObjectShape containing variable name-value pairs from Fluent code
607
+ * @param type - Optional type flag ('variables' for variables array, default for inputs array)
608
+ * @returns Array of SetFlowVariablesInput entries ready for XML serialization
609
+ *
610
+ * @example
611
+ * // Fluent: { user: params.trigger.name, count: 42 }
612
+ * // Variables array: [{ name: 'user', value: '{{trigger.name}}', displayValue: '{{trigger.name}}' }, ...]
613
+ * // Inputs array: [{ name: 'user', value: '{{trigger.name}}', displayValue: '' }, ...]
614
+ */
615
+ function prepareSetFlowVariables(values: ObjectShape, type: string = ''): SetFlowVariablesInput[] {
616
+ return prepareEntries(values, (key, value, children) => {
617
+ // Choose appropriate creator based on type flag
618
+ if (type === 'variables') {
619
+ return createVariable(key, value, children)
620
+ } else {
621
+ return createVariableInput(key, value, children)
622
+ }
623
+ })
624
+ }
625
+
626
+ /**
627
+ * Prepares AssignSubflowOutputs data for XML serialization.
628
+ * Converts an ObjectShape containing output assignments into an array of AssignSubflowOutput entries.
629
+ *
630
+ * @param values - ObjectShape containing output name-value pairs from Fluent code
631
+ * @returns Array of AssignSubflowOutput entries ready for XML serialization
632
+ *
633
+ * @example
634
+ * // Fluent: { result: params.action.output, status: 'complete' }
635
+ * // XML: [{ name: 'result', value: '{{action.output}}', displayValue: '{{action.output}}', scriptActive: false }, ...]
636
+ *
637
+ * Note: childName for FlowArray fields is now set automatically by resolveComplexInput in postProcess functions
638
+ */
639
+ function prepareAssignSubflowOutputs(values: ObjectShape): AssignSubflowOutput[] {
640
+ return prepareEntries(values, createOutput)
641
+ }
642
+
643
+ // Factory pattern for value parsing
644
+ type ValueParser = (inputs: FlowLogicValueInput[]) => FlowLogicParseResult
645
+
646
+ const VALUE_PARSERS: { [K in FLOW_LOGIC]: ValueParser } = {
647
+ [FLOW_LOGIC.GOBACKTO]: parseGoBackTo,
648
+ [FLOW_LOGIC.IF]: parseIfElse,
649
+ [FLOW_LOGIC.ELSEIF]: parseIfElse,
650
+ [FLOW_LOGIC.ELSE]: parseIfElse,
651
+ [FLOW_LOGIC.WAIT_FOR_A_DURATION]: () => ({}),
652
+ [FLOW_LOGIC.EXITLOOP]: () => ({}),
653
+ [FLOW_LOGIC.ENDFLOW]: () => ({}),
654
+ [FLOW_LOGIC.SKIP_ITERATION]: () => ({}),
655
+ [FLOW_LOGIC.FOR_EACH]: parseForEach,
656
+ [FLOW_LOGIC.DO_IN_PARALLEL]: () => ({}),
657
+ [FLOW_LOGIC.TRY_CATCH]: () => ({}),
658
+ [FLOW_LOGIC.SET_FLOW_VARIABLES]: () => ({}),
659
+ [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: () => ({}),
660
+ }
661
+
662
+ /**
663
+ * Converts ServiceNow XML flow logic records back to Fluent TypeScript.
664
+ *
665
+ * **Process:**
666
+ * 1. Takes gzipped JSON string from XML record
667
+ * 2. Unzips and parses the FlowLogicValues structure
668
+ * 3. Uses the appropriate VALUE_PARSER function
669
+ * 4. Reconstructs the original configuration object
670
+ * 5. Returns structured data for shape generation
671
+ *
672
+ * **Example:**
673
+ * ```typescript
674
+ * // Input: gzipped JSON with inputs array
675
+ * // Output: { condition: "user.role === 'admin'", label: "Admin Check" /**
676
+ * Parses flow logic values from gzipped JSON string
677
+ * @param logicType - The flow logic type key (e.g., 'IF', 'ELSEIF', 'GOBACKTO')
678
+ * @param gZippedString - Base64 encoded gzipped JSON from XML record
679
+ * @param typeMap - Optional map of variable/output names to their internal types for proper type conversion
680
+ * @param labelCacheMap - Optional map of datapill names to their types from the label cache
681
+ * @returns Parsed configuration object for TypeScript shape generation
682
+ */
683
+ export function parse(
684
+ logicType: FLOW_LOGIC,
685
+ gZippedString: string,
686
+ typeMap?: Map<string, string>,
687
+ labelCacheMap?: Map<string, string>
688
+ ): FlowLogicParseResult {
689
+ const valuesJson = parseGzippedJson(gZippedString)
690
+
691
+ // Special handling for SET_FLOW_VARIABLES - data is in variables array
692
+ if (logicType === FLOW_LOGIC.SET_FLOW_VARIABLES) {
693
+ if (valuesJson?.variables?.length) {
694
+ return parseEntriesFromArray(valuesJson.variables, typeMap, labelCacheMap)
695
+ }
696
+ return {}
697
+ }
698
+
699
+ // Special handling for ASSIGN_SUBFLOW_OUTPUTS - data is in outputsToAssign array
700
+ if (logicType === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS) {
701
+ if (valuesJson?.outputsToAssign?.length) {
702
+ return parseEntriesFromArray(valuesJson.outputsToAssign, typeMap, labelCacheMap)
703
+ }
704
+ return {}
705
+ }
706
+
707
+ if (!valuesJson?.inputs?.length) {
708
+ if (logicType === FLOW_LOGIC.GOBACKTO) {
709
+ throw new Error('Malformed/unsupported input payload in GoBackTo Instance')
710
+ }
711
+ return {}
712
+ }
713
+
714
+ const parser = VALUE_PARSERS[logicType]
715
+ if (!parser) {
716
+ throw new Error(`Unsupported flow logic type for parsing: ${logicType}`)
717
+ }
718
+
719
+ return parser(valuesJson.inputs)
720
+ }
721
+
722
+ function parseGzippedJson(gZippedString: string): FlowLogicValues | null {
723
+ try {
724
+ const unzipped = gunzipSync(Buffer.from(gZippedString, 'base64')).toString()
725
+ return JSON.parse(unzipped)
726
+ } catch {
727
+ return null
728
+ }
729
+ }
730
+
731
+ function parseIfElse(inputs: FlowLogicValueInput[]): { condition?: string; label?: string } {
732
+ const result: { condition?: string; label?: string } = {}
733
+
734
+ for (const input of inputs) {
735
+ if (input.name === 'condition' && input.value) {
736
+ result.condition = input.value
737
+ } else if (input.name === 'condition_name' && input.value) {
738
+ result.label = input.value
739
+ }
740
+ }
741
+
742
+ return result
743
+ }
744
+
745
+ function parseGoBackTo(inputs: FlowLogicValueInput[]): string {
746
+ for (const input of inputs) {
747
+ if (input.name === 'go_back_to_step' && input.value) {
748
+ return input.value
749
+ }
750
+ }
751
+ throw new Error('Malformed/unsupported input payload in GoBackTo Instance')
752
+ }
753
+
754
+ function parseForEach(inputs: FlowLogicValueInput[]): { items?: string } {
755
+ const result: { items?: string } = {}
756
+
757
+ for (const input of inputs) {
758
+ if (input.name === 'items' && input.value) {
759
+ result.items = input.value
760
+ }
761
+ }
762
+
763
+ return result
764
+ }
765
+
766
+ // Constants for type handling
767
+ const ARRAY_TYPE_PREFIX = 'array.' as const
768
+ const ARRAY_TYPE_PREFIX_LENGTH = ARRAY_TYPE_PREFIX.length
769
+ const EXCLUDED_TYPE_ANNOTATIONS = new Set(['string', 'glide_date_time'])
770
+
771
+ /**
772
+ * Interface for child entry structure used in parsing
773
+ */
774
+ interface ChildEntry {
775
+ name?: string
776
+ value?: string
777
+ children?: unknown[]
778
+ parameter?: {
779
+ type?: string
780
+ }
781
+ }
782
+
783
+ /**
784
+ * Extracts the base name from a dotted path (e.g., "project.name" -> "name")
785
+ * For assignSubflowOutputs, names are prefixed with parent (e.g., "project.name", "project.tasks")
786
+ * We need to strip the prefix to get the actual field name
787
+ * @param name - Full path name
788
+ * @returns Base name after the last dot
789
+ */
790
+ function getBaseName(name?: string): string {
791
+ if (!name) {
792
+ return ''
793
+ }
794
+ const lastDot = name.lastIndexOf('.')
795
+ return lastDot >= 0 ? name.substring(lastDot + 1) : name
796
+ }
797
+
798
+ /**
799
+ * Extracts element type from array type string
800
+ * @param type - Type string like "array.integer" or "string"
801
+ * @returns Element type or undefined
802
+ */
803
+ function extractArrayElementType(type?: string): string | undefined {
804
+ if (!type || !type.startsWith(ARRAY_TYPE_PREFIX)) {
805
+ return undefined
806
+ }
807
+ return type.substring(ARRAY_TYPE_PREFIX_LENGTH)
808
+ }
809
+
810
+ /**
811
+ * Strip type annotation from datapill strings.
812
+ * ServiceNow stores datapills with type annotations like {{trigger.name|string}}.
813
+ * We need to remove the |type part before creating PropertyAccessShape.
814
+ *
815
+ * @param value - The value to clean (may be datapill string or regular value)
816
+ * @returns Cleaned value with type annotation removed
817
+ */
818
+ function stripDatapillTypeAnnotation(value: string): string {
819
+ // Check if this is a datapill string (starts with {{ and ends with }})
820
+ if (value.startsWith('{{') && value.endsWith('}}')) {
821
+ // Extract content between {{ and }}
822
+ const content = value.slice(2, -2)
823
+ // Split by | and take only the first part (the path without type annotation)
824
+ const pathOnly = content.split('|')[0]
825
+ // Return cleaned datapill
826
+ return `{{${pathOnly}}}`
827
+ }
828
+ // Not a datapill, return as-is
829
+ return value
830
+ }
831
+
832
+ /**
833
+ * Adds type annotation to a datapill if needed based on label cache
834
+ * @param pillName - Datapill name without braces
835
+ * @param labelCacheMap - Map of datapill names to types
836
+ * @returns Datapill with annotation if applicable
837
+ */
838
+ function addTypeAnnotationIfNeeded(pillName: string, labelCacheMap?: Map<string, string>): string {
839
+ if (!labelCacheMap) {
840
+ return `{{${pillName}}}`
841
+ }
842
+
843
+ const cachedType = labelCacheMap.get(pillName.trim())
844
+ if (cachedType && !EXCLUDED_TYPE_ANNOTATIONS.has(cachedType)) {
845
+ return `{{${pillName}|${cachedType}}}`
846
+ }
847
+
848
+ return `{{${pillName}}}`
849
+ }
850
+
851
+ /**
852
+ * Processes a pure datapill value (starts and ends with {{}})
853
+ * @param value - Pure datapill string
854
+ * @param labelCacheMap - Map of datapill names to types
855
+ * @returns Processed datapill with type annotation if needed
856
+ */
857
+ function processPureDatapill(value: string, labelCacheMap?: Map<string, string>): string {
858
+ const strippedValue = stripDatapillTypeAnnotation(value)
859
+ const pillName = strippedValue.slice(2, -2) // Remove {{ and }}
860
+ return addTypeAnnotationIfNeeded(pillName, labelCacheMap)
861
+ }
862
+
863
+ /**
864
+ * Processes a template expression containing datapills mixed with text
865
+ * @param value - Template expression string
866
+ * @param labelCacheMap - Map of datapill names to types
867
+ * @returns Processed template with type annotations added to datapills
868
+ */
869
+ function processTemplateExpression(value: string, labelCacheMap?: Map<string, string>): string {
870
+ if (!labelCacheMap) {
871
+ return value
872
+ }
873
+
874
+ const datapillRegex = /\{\{([^}|]+)(?:\|[^}]+)?\}\}/g
875
+ return value.replace(datapillRegex, (_match, pillName) => addTypeAnnotationIfNeeded(pillName, labelCacheMap))
876
+ }
877
+
878
+ /**
879
+ * Converts a primitive value based on its type
880
+ * @param value - String value to convert
881
+ * @param uiType - Type information
882
+ * @returns Converted value
883
+ */
884
+ function convertPrimitiveValue(value: string, uiType?: string): unknown {
885
+ // Preserve empty strings - don't convert them to 0 or false
886
+ if (value === '') {
887
+ return value
888
+ }
889
+
890
+ if (uiType === 'integer') {
891
+ const num = Number(value)
892
+ return Number.isNaN(num) ? value : num
893
+ }
894
+
895
+ if (uiType === 'boolean') {
896
+ if (value === 'true' || value === '1') {
897
+ return true
898
+ }
899
+ if (value === 'false' || value === '0') {
900
+ return false
901
+ }
902
+ return value
903
+ }
904
+
905
+ return stripDatapillTypeAnnotation(value)
906
+ }
907
+
908
+ /**
909
+ * Recursively applies type conversion to an object and its nested objects
910
+ * @param obj - Object to apply type conversion to
911
+ * @param typeMap - Map of field names to their types
912
+ * @returns Object with type-converted values
913
+ */
914
+ function applyTypeConversionToObject(obj: unknown, typeMap: Map<string, string>): unknown {
915
+ if (typeof obj !== 'object' || obj === null) {
916
+ return obj
917
+ }
918
+ if (Array.isArray(obj)) {
919
+ return obj
920
+ }
921
+ const result: { [key: string]: unknown } = {}
922
+ for (const [key, value] of Object.entries(obj)) {
923
+ const fieldType = typeMap.get(key)
924
+ if (typeof value === 'string' && fieldType) {
925
+ result[key] = convertPrimitiveValue(value, fieldType)
926
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
927
+ // Recursively apply to nested objects
928
+ result[key] = applyTypeConversionToObject(value, typeMap)
929
+ } else {
930
+ result[key] = value
931
+ }
932
+ }
933
+ return result
934
+ }
935
+
936
+ /**
937
+ * Converts a string value to the appropriate type based on uiType
938
+ * @param value - String value to convert
939
+ * @param uiType - Type information from parameter (e.g., 'integer', 'boolean', 'string')
940
+ * @param labelCacheMap - Optional map of datapill names to their types from the label cache
941
+ * @returns Properly typed value
942
+ */
943
+ function convertChildValueToType(value: string, uiType?: string, labelCacheMap?: Map<string, string>): unknown {
944
+ // Check if value contains datapills (either pure datapill or template expression)
945
+ if (value.includes('{{') && value.includes('}}')) {
946
+ if (value.startsWith('{{') && value.endsWith('}}')) {
947
+ // Pure datapill
948
+ return processPureDatapill(value, labelCacheMap)
949
+ }
950
+ // Template expression with datapills
951
+ return processTemplateExpression(value, labelCacheMap)
952
+ }
953
+
954
+ // Non-datapill value - convert based on type
955
+ return convertPrimitiveValue(value, uiType)
956
+ }
957
+
958
+ /**
959
+ * Extracts the element type from a parent entry for primitive arrays.
960
+ * Parses the complexObjectSchema from the parent's value to get child_type.
961
+ *
962
+ * @param parentEntry - Parent entry containing the schema information
963
+ * @returns Element type (e.g., 'integer', 'boolean', 'string') or undefined
964
+ */
965
+ function getArrayElementType(parentEntry: { value?: unknown }): string | undefined {
966
+ if (!parentEntry.value || typeof parentEntry.value !== 'string') {
967
+ return undefined
968
+ }
969
+
970
+ try {
971
+ const parsed = JSON.parse(parentEntry.value)
972
+ if (parsed.complexObjectSchema) {
973
+ // Find the $COCollectionField.$type_facets
974
+ for (const schemaKey in parsed.complexObjectSchema) {
975
+ const schema = parsed.complexObjectSchema[schemaKey]
976
+ if (schema && schema.$COCollectionField && schema['$COCollectionField.$type_facets']) {
977
+ const typeFacets = schema['$COCollectionField.$type_facets']
978
+ if (typeFacets.SimpleMapFacet) {
979
+ const facetData = JSON.parse(typeFacets.SimpleMapFacet)
980
+ return facetData.child_type
981
+ }
982
+ }
983
+ }
984
+ }
985
+ } catch {
986
+ // Ignore parsing errors
987
+ }
988
+ return undefined
989
+ }
990
+
991
+ /**
992
+ * Extracts type schema from a parent entry's complexObjectSchema.
993
+ * The schema contains nested field type information that can be used for type conversion.
994
+ *
995
+ * @param parentEntry - Parent entry containing the schema in its value field
996
+ * @returns Map of field paths to their types (e.g., 'timeout' -> 'integer')
997
+ */
998
+ function extractNestedTypeSchema(parentEntry: { value?: unknown }): Map<string, string> | undefined {
999
+ const typeMap = new Map<string, string>()
1000
+
1001
+ if (!parentEntry.value || typeof parentEntry.value !== 'string') {
1002
+ return undefined
1003
+ }
1004
+
1005
+ try {
1006
+ const parsed = JSON.parse(parentEntry.value)
1007
+ const complexObjectSchema = parsed.complexObjectSchema
1008
+
1009
+ if (!complexObjectSchema) {
1010
+ return undefined
1011
+ }
1012
+
1013
+ // Iterate through schema keys (e.g., "FlowDesigner:FD8fc76f46892b4980af21eb5f7b164ef2")
1014
+ for (const schemaKey in complexObjectSchema) {
1015
+ const schema = complexObjectSchema[schemaKey]
1016
+ if (!schema || typeof schema !== 'object') {
1017
+ continue
1018
+ }
1019
+
1020
+ // Extract field types from the schema
1021
+ for (const fieldName in schema) {
1022
+ if (fieldName.endsWith('.$field_facets') || fieldName.endsWith('.$type_facets')) {
1023
+ continue
1024
+ }
1025
+
1026
+ const fieldValue = schema[fieldName]
1027
+
1028
+ // Check if this is a nested object with its own fields
1029
+ if (typeof fieldValue === 'object' && fieldValue !== null) {
1030
+ // Nested object - recursively extract types
1031
+ for (const nestedFieldName in fieldValue) {
1032
+ if (nestedFieldName.endsWith('.$field_facets')) {
1033
+ continue
1034
+ }
1035
+ const nestedType = fieldValue[nestedFieldName]
1036
+ if (typeof nestedType === 'string') {
1037
+ // Map nested field names (e.g., 'timeout' under 'settings')
1038
+ typeMap.set(nestedFieldName, nestedType.toLowerCase())
1039
+ }
1040
+ }
1041
+ } else if (typeof fieldValue === 'string') {
1042
+ // Direct type (e.g., "Integer", "Boolean", "String")
1043
+ typeMap.set(fieldName, fieldValue.toLowerCase())
1044
+ }
1045
+ }
1046
+ }
1047
+
1048
+ return typeMap.size > 0 ? typeMap : undefined
1049
+ } catch {
1050
+ // Ignore parsing errors
1051
+ return undefined
1052
+ }
1053
+ }
1054
+
1055
+ /**
1056
+ * Recursively parses children array to reconstruct nested objects and arrays
1057
+ * @param children - Array of child entries with name, value, and optional nested children
1058
+ * @param elementType - Type hint for array elements (e.g., 'string', 'integer')
1059
+ * @param nestedTypeMap - Optional map of field names to types for nested FlowObjects
1060
+ * @param complexObjectData - Optional complex object data for merging with children
1061
+ * @param labelCacheMap - Optional map of datapill names to their types from the label cache
1062
+ * @returns Reconstructed nested object or array
1063
+ */
1064
+ function parseChildrenArray(
1065
+ children: unknown[],
1066
+ elementType?: string,
1067
+ nestedTypeMap?: Map<string, string>,
1068
+ complexObjectData?: unknown,
1069
+ labelCacheMap?: Map<string, string>
1070
+ ): unknown {
1071
+ if (!Array.isArray(children) || children.length === 0) {
1072
+ return undefined
1073
+ }
1074
+
1075
+ // Check if this is an array or object:
1076
+ // - Array (SDK): all children have empty names
1077
+ // - Array (UI): all children have the SAME non-empty name (childName repeated)
1078
+ // - Object: children have different names (field names)
1079
+ const childObj = children[0] as ChildEntry
1080
+
1081
+ const firstBaseName = getBaseName(childObj.name)
1082
+
1083
+ // Check if all children have the same base name (indicating FlowArray with repeated childName)
1084
+ const allSameName =
1085
+ children.length > 1 &&
1086
+ children.every((c) => {
1087
+ const entry = c as ChildEntry
1088
+ return getBaseName(entry.name) === firstBaseName
1089
+ })
1090
+
1091
+ // Check if the base name is empty (array elements without explicit names)
1092
+ const hasEmptyBaseName = !firstBaseName || firstBaseName === ''
1093
+
1094
+ const isArray = hasEmptyBaseName || allSameName
1095
+
1096
+ if (isArray) {
1097
+ // FlowArray: reconstruct array of elements
1098
+ const result: unknown[] = []
1099
+ for (const child of children) {
1100
+ const childEntry = child as ChildEntry
1101
+ if (childEntry.children && childEntry.children.length > 0) {
1102
+ // Nested object or array - get corresponding complexObject data for this element
1103
+ let elementComplexData: unknown | undefined
1104
+ if (Array.isArray(complexObjectData) && result.length < complexObjectData.length) {
1105
+ elementComplexData = complexObjectData[result.length]
1106
+ }
1107
+ result.push(
1108
+ parseChildrenArray(childEntry.children, undefined, undefined, elementComplexData, labelCacheMap)
1109
+ )
1110
+ } else if (childEntry.value !== undefined && childEntry.value !== null && childEntry.value !== '') {
1111
+ // Primitive value - convert to proper type
1112
+ // Use parameter type if available, otherwise use passed elementType
1113
+ const uiType = childEntry.parameter?.type || elementType
1114
+ const convertedValue =
1115
+ typeof childEntry.value === 'string'
1116
+ ? convertChildValueToType(childEntry.value, uiType, labelCacheMap)
1117
+ : childEntry.value
1118
+ result.push(convertedValue)
1119
+ } else if (Array.isArray(complexObjectData) && result.length < complexObjectData.length) {
1120
+ // Value is empty/null, try to get from complexObject
1121
+ result.push(complexObjectData[result.length])
1122
+ }
1123
+ }
1124
+ return result
1125
+ } else {
1126
+ // FlowObject: reconstruct object with named fields
1127
+ const result: { [key: string]: unknown } = {}
1128
+
1129
+ for (const child of children) {
1130
+ const childEntry = child as ChildEntry
1131
+ if (!childEntry.name) {
1132
+ continue
1133
+ }
1134
+ // Strip parent prefix from field name (e.g., "project.name" -> "name")
1135
+ // This is needed for assignSubflowOutputs where names are prefixed with parent
1136
+ const fieldName = getBaseName(childEntry.name)
1137
+
1138
+ if (childEntry.children && childEntry.children.length > 0) {
1139
+ // Nested object or array
1140
+ // Extract element type from parameter.type if this is an array (e.g., "array.integer" -> "integer")
1141
+ const childElementType = extractArrayElementType(childEntry.parameter?.type)
1142
+
1143
+ // Extract nested complex object data for this field
1144
+ let nestedComplexData: unknown
1145
+ if (
1146
+ complexObjectData &&
1147
+ typeof complexObjectData === 'object' &&
1148
+ !Array.isArray(complexObjectData)
1149
+ ) {
1150
+ const objData = complexObjectData as { [key: string]: unknown }
1151
+ nestedComplexData = objData[fieldName]
1152
+ }
1153
+
1154
+ // Extract type schema for this nested child
1155
+ // Try to get schema from the child entry itself, or fall back to parent's type map
1156
+ const childTypeMap = extractNestedTypeSchema(childEntry) || nestedTypeMap
1157
+
1158
+ result[fieldName] = parseChildrenArray(
1159
+ childEntry.children,
1160
+ childElementType,
1161
+ childTypeMap,
1162
+ nestedComplexData,
1163
+ labelCacheMap
1164
+ )
1165
+ } else if (childEntry.value !== undefined && childEntry.value !== null && childEntry.value !== '') {
1166
+ // Primitive value - convert to proper type
1167
+ // Try parameter type first, then nested type map, then no type
1168
+ const uiType = childEntry.parameter?.type || nestedTypeMap?.get(childEntry.name)
1169
+ const convertedValue =
1170
+ typeof childEntry.value === 'string'
1171
+ ? convertChildValueToType(childEntry.value, uiType, labelCacheMap)
1172
+ : childEntry.value
1173
+ result[fieldName] = convertedValue
1174
+ } else if (
1175
+ complexObjectData &&
1176
+ typeof complexObjectData === 'object' &&
1177
+ !Array.isArray(complexObjectData)
1178
+ ) {
1179
+ // No value in children, try to get from complexObject
1180
+ const objData = complexObjectData as { [key: string]: unknown }
1181
+ if (fieldName in objData) {
1182
+ const value = objData[fieldName]
1183
+ // Apply type conversion if we have type information
1184
+ const uiType = nestedTypeMap?.get(childEntry.name) || nestedTypeMap?.get(fieldName)
1185
+ if (typeof value === 'string' && uiType) {
1186
+ result[fieldName] = convertPrimitiveValue(value, uiType)
1187
+ } else {
1188
+ result[fieldName] = value
1189
+ }
1190
+ }
1191
+ } else if (childEntry.parameter?.type && childEntry.parameter.type.startsWith('array')) {
1192
+ // Array field with no children or value - initialize as empty array
1193
+ result[fieldName] = []
1194
+ }
1195
+ }
1196
+
1197
+ // Merge any fields from complexObject that aren't in children
1198
+ // This handles primitive arrays that are only in complexObject
1199
+ if (complexObjectData && typeof complexObjectData === 'object' && !Array.isArray(complexObjectData)) {
1200
+ const objData = complexObjectData as { [key: string]: unknown }
1201
+ for (const key of Object.keys(objData)) {
1202
+ if (!(key in result)) {
1203
+ result[key] = objData[key]
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ return result
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Extracts plain values from ServiceNow's $cv wrapped format.
1214
+ */
1215
+ function extractComplexObjectValues(complexObject: unknown): unknown {
1216
+ if (typeof complexObject !== 'object' || complexObject === null) {
1217
+ return complexObject
1218
+ }
1219
+
1220
+ const obj = complexObject as { $cv?: { $v?: unknown }; $COCollectionField?: unknown }
1221
+ if (obj.$cv && '$v' in obj.$cv) {
1222
+ return obj.$cv.$v
1223
+ }
1224
+
1225
+ if (obj.$COCollectionField) {
1226
+ return extractComplexObjectValues(obj.$COCollectionField)
1227
+ }
1228
+
1229
+ if (Array.isArray(complexObject)) {
1230
+ return complexObject.map((item) => extractComplexObjectValues(item))
1231
+ }
1232
+
1233
+ const result: { [key: string]: unknown } = {}
1234
+ for (const [key, value] of Object.entries(complexObject)) {
1235
+ if (key === 'name$' || key === '$COCollectionField') {
1236
+ continue
1237
+ }
1238
+ result[key] = extractComplexObjectValues(value)
1239
+ }
1240
+ return result
1241
+ }
1242
+
1243
+ /**
1244
+ * Reconstructs FlowObject/FlowArray from complexObjectSchema with mapped datapills.
1245
+ * This handles the UI format where children array is empty but datapills are stored in facets.
1246
+ */
1247
+ function reconstructFromMappedFacets(schema: unknown, complexObject: unknown): unknown {
1248
+ if (typeof schema !== 'object' || schema === null) {
1249
+ return null
1250
+ }
1251
+
1252
+ const schemaObj = schema as { [key: string]: unknown }
1253
+ const typeKey = Object.keys(schemaObj).find((k) => k.startsWith('FlowDesigner:'))
1254
+ if (!typeKey) {
1255
+ return null
1256
+ }
1257
+
1258
+ const template = schemaObj[typeKey]
1259
+ if (typeof template !== 'object' || template === null) {
1260
+ return null
1261
+ }
1262
+
1263
+ const templateObj = template as { [key: string]: unknown }
1264
+ const literalValues = extractComplexObjectValues(complexObject)
1265
+ const mappings = extractMappingsFromFacets(templateObj)
1266
+
1267
+ if (mappings.size === 0) {
1268
+ return literalValues
1269
+ }
1270
+
1271
+ return applyMappingsToValues(literalValues, mappings)
1272
+ }
1273
+
1274
+ /**
1275
+ * Extracts datapill mappings from field facets in the schema template.
1276
+ * Recursively searches through nested objects and arrays to find all $field_facets.
1277
+ */
1278
+ function extractMappingsFromFacets(template: { [key: string]: unknown }): Map<string, string> {
1279
+ const mappings = new Map<string, string>()
1280
+
1281
+ function extractFromObject(obj: unknown): void {
1282
+ if (typeof obj !== 'object' || obj === null) {
1283
+ return
1284
+ }
1285
+
1286
+ if (Array.isArray(obj)) {
1287
+ for (const item of obj) {
1288
+ extractFromObject(item)
1289
+ }
1290
+ return
1291
+ }
1292
+
1293
+ const objMap = obj as { [key: string]: unknown }
1294
+ for (const key of Object.keys(objMap)) {
1295
+ if (key.endsWith('.$field_facets')) {
1296
+ const facets = objMap[key]
1297
+ if (typeof facets !== 'object' || facets === null) {
1298
+ continue
1299
+ }
1300
+
1301
+ const facetsObj = facets as { SimpleMapFacet?: string }
1302
+ const simpleMapFacetStr = facetsObj.SimpleMapFacet
1303
+
1304
+ if (typeof simpleMapFacetStr === 'string') {
1305
+ try {
1306
+ const facetObj = JSON.parse(simpleMapFacetStr) as { mapped?: string }
1307
+ if (facetObj.mapped) {
1308
+ const mappedObj = JSON.parse(facetObj.mapped) as { [key: string]: string }
1309
+ for (const [path, datapill] of Object.entries(mappedObj)) {
1310
+ mappings.set(path, stripDatapillTypeAnnotation(datapill))
1311
+ }
1312
+ }
1313
+ } catch {
1314
+ // Skip invalid facets
1315
+ }
1316
+ }
1317
+ } else {
1318
+ // Recursively search nested objects
1319
+ extractFromObject(objMap[key])
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ extractFromObject(template)
1325
+ return mappings
1326
+ }
1327
+
1328
+ /**
1329
+ * Applies datapill mappings to literal values by setting values at dotted paths.
1330
+ */
1331
+ function applyMappingsToValues(values: unknown, mappings: Map<string, string>): unknown {
1332
+ const result = JSON.parse(JSON.stringify(values))
1333
+
1334
+ for (const [path, datapill] of mappings.entries()) {
1335
+ setValueAtPath(result, path, datapill)
1336
+ }
1337
+
1338
+ return result
1339
+ }
1340
+
1341
+ /**
1342
+ * Sets a value at a dotted path in an object/array structure.
1343
+ * Handles array indices like "tasks[0].dueDate".
1344
+ */
1345
+ function setValueAtPath(obj: unknown, path: string, value: unknown): void {
1346
+ const parts = path.split(/\.|\[|\]/).filter((p) => p !== '')
1347
+ let current = obj as { [key: string]: unknown } | unknown[]
1348
+
1349
+ for (let i = 0; i < parts.length - 1; i++) {
1350
+ const part = parts[i]
1351
+ if (!part) {
1352
+ continue
1353
+ }
1354
+ const nextPart = parts[i + 1]
1355
+
1356
+ if (nextPart && /^\d+$/.test(nextPart)) {
1357
+ if (Array.isArray(current)) {
1358
+ const idx = Number.parseInt(part, 10)
1359
+ if (!current[idx]) {
1360
+ current[idx] = {}
1361
+ }
1362
+ current = current[idx] as { [key: string]: unknown } | unknown[]
1363
+ } else {
1364
+ if (!Array.isArray(current[part])) {
1365
+ current[part] = []
1366
+ }
1367
+ current = current[part] as unknown[]
1368
+ }
1369
+ } else if (/^\d+$/.test(part)) {
1370
+ const idx = Number.parseInt(part, 10)
1371
+ if (Array.isArray(current)) {
1372
+ if (!current[idx]) {
1373
+ current[idx] = {}
1374
+ }
1375
+ current = current[idx] as { [key: string]: unknown }
1376
+ }
1377
+ } else {
1378
+ if (Array.isArray(current)) {
1379
+ const idx = Number.parseInt(part, 10)
1380
+ if (!current[idx]) {
1381
+ current[idx] = {}
1382
+ }
1383
+ current = current[idx] as { [key: string]: unknown }
1384
+ } else {
1385
+ if (typeof current[part] !== 'object' || current[part] === null) {
1386
+ current[part] = {}
1387
+ }
1388
+ current = current[part] as { [key: string]: unknown }
1389
+ }
1390
+ }
1391
+ }
1392
+
1393
+ const lastPart = parts[parts.length - 1]
1394
+ if (lastPart) {
1395
+ if (/^\d+$/.test(lastPart)) {
1396
+ const idx = Number.parseInt(lastPart, 10)
1397
+ if (Array.isArray(current)) {
1398
+ current[idx] = value
1399
+ }
1400
+ } else {
1401
+ if (!Array.isArray(current)) {
1402
+ current[lastPart] = value
1403
+ }
1404
+ }
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * Generic helper to parse an array of entries with name-value pairs into an object.
1410
+ * Converts string values to appropriate types using type information from the schema.
1411
+ *
1412
+ * **Inline Script Support:**
1413
+ * Detects entries with `scriptActive: true` and creates FDInlineScriptCallShape objects
1414
+ * that represent `wfa.inlineScript('...')` calls in the generated Fluent code.
1415
+ *
1416
+ * **FlowObject/FlowArray Support:**
1417
+ * Detects entries with non-empty `children` arrays and recursively parses them back
1418
+ * into nested objects or arrays, reversing the buildChildrenArray operation.
1419
+ *
1420
+ * @param entries - Array of entries with name, value, and optional scriptActive/script properties
1421
+ * @param typeMap - Optional map of names to their internal types for proper type conversion
1422
+ * @param record - Optional source Record for creating FDInlineScriptCallShape with proper source
1423
+ * @returns Object with names as keys and properly typed values or FDInlineScriptCallShape for inline scripts
1424
+ */
1425
+ function parseEntriesFromArray<
1426
+ T extends {
1427
+ name?: string
1428
+ value?: unknown
1429
+ scriptActive?: boolean
1430
+ script?: { [key: string]: { scriptActive: boolean; script: string } }
1431
+ children?: unknown[]
1432
+ },
1433
+ >(entries: T[], typeMap?: Map<string, string>, labelCacheMap?: Map<string, string>): { [key: string]: unknown } {
1434
+ const result: { [key: string]: unknown } = {}
1435
+
1436
+ for (const entry of entries) {
1437
+ if (!entry.name) {
1438
+ continue
1439
+ }
1440
+
1441
+ // Check if this is an inline script (matches flow-instance-plugin.ts pattern)
1442
+ // Return script metadata that buildVariableOrOutputArguments will recognize
1443
+ if (entry.scriptActive === true && entry.script) {
1444
+ const scriptData = entry.script[entry.name]
1445
+ if (scriptData?.script) {
1446
+ // Return metadata object (not a Shape) so it can be processed later
1447
+ result[entry.name] = {
1448
+ scriptActive: true,
1449
+ scriptContent: scriptData.script,
1450
+ }
1451
+ continue
1452
+ }
1453
+ }
1454
+
1455
+ // Check if this entry has children (FlowObject/FlowArray)
1456
+ if (entry.children && Array.isArray(entry.children) && entry.children.length > 0) {
1457
+ // Special case: empty arrays are represented as a single child with empty string value
1458
+ // Empty arrays have: no name, empty string value, and undefined children (not empty array)
1459
+ // Arrays with objects (like [{item: ''}]) have children with names and/or children arrays
1460
+ const firstChild = entry.children[0] as { name?: string; value?: string; children?: unknown[] }
1461
+ const isEmptyArray =
1462
+ entry.children.length === 1 &&
1463
+ firstChild.value === '' &&
1464
+ !firstChild.name && // Empty arrays have no name on the child element
1465
+ firstChild.children === undefined // Empty arrays have undefined children, not empty array
1466
+
1467
+ if (isEmptyArray) {
1468
+ result[entry.name] = []
1469
+ continue
1470
+ }
1471
+
1472
+ // If parent value is a datapill (pure or template expression), use it directly and ignore children
1473
+ // This handles the case where a datapill is assigned to a FlowObject variable
1474
+ // Check for datapills: starts with {{ (pure datapill) or contains {{ but not starting with { (template expression)
1475
+ const isDatapillValue =
1476
+ entry.value &&
1477
+ typeof entry.value === 'string' &&
1478
+ (entry.value.startsWith('{{') ||
1479
+ (entry.value.includes('{{') && entry.value.includes('}}') && !entry.value.startsWith('{')))
1480
+
1481
+ if (isDatapillValue) {
1482
+ const uiType = typeMap?.get(entry.name)
1483
+ const convertedValue = convertChildValueToType(entry.value as string, uiType, labelCacheMap)
1484
+ result[entry.name] = convertedValue
1485
+ continue
1486
+ }
1487
+
1488
+ // Try to extract actual values from complexObject field if available
1489
+ if (entry.value && typeof entry.value === 'string' && !childrenContainDatapills(entry.children)) {
1490
+ try {
1491
+ const parsed = JSON.parse(entry.value)
1492
+ if (parsed.complexObject) {
1493
+ const extractedValue = extractComplexObjectValues(parsed.complexObject)
1494
+
1495
+ // Apply type conversion using schema information
1496
+ const nestedTypeMap = extractNestedTypeSchema(entry)
1497
+ if (
1498
+ nestedTypeMap &&
1499
+ typeof extractedValue === 'object' &&
1500
+ extractedValue !== null &&
1501
+ !Array.isArray(extractedValue)
1502
+ ) {
1503
+ // For FlowObject, apply type conversion to each field (including nested objects)
1504
+ result[entry.name] = applyTypeConversionToObject(extractedValue, nestedTypeMap)
1505
+ } else {
1506
+ result[entry.name] = extractedValue
1507
+ }
1508
+ continue
1509
+ }
1510
+ } catch {
1511
+ // Fall through to children parsing
1512
+ }
1513
+ }
1514
+
1515
+ // Fallback: Parse structure from children array
1516
+ // Extract complexObject data if available for merging with children
1517
+ let complexObjData: unknown
1518
+ if (entry.value && typeof entry.value === 'string') {
1519
+ try {
1520
+ const parsed = JSON.parse(entry.value)
1521
+ if (parsed.complexObject) {
1522
+ complexObjData = extractComplexObjectValues(parsed.complexObject)
1523
+ }
1524
+ } catch {
1525
+ // Ignore parse errors
1526
+ }
1527
+ }
1528
+ const elementType = getArrayElementType(entry)
1529
+ const nestedTypeMap = extractNestedTypeSchema(entry)
1530
+ const parsedValue = parseChildrenArray(
1531
+ entry.children,
1532
+ elementType,
1533
+ nestedTypeMap,
1534
+ complexObjData,
1535
+ labelCacheMap
1536
+ )
1537
+ result[entry.name] = parsedValue
1538
+ continue
1539
+ }
1540
+
1541
+ // Handle UI format: children empty but complexObjectSchema has mapped datapills in facets
1542
+ if (entry.value && typeof entry.value === 'string') {
1543
+ try {
1544
+ const parsed = JSON.parse(entry.value)
1545
+ if (parsed.complexObjectSchema && parsed.complexObject) {
1546
+ // Extract datapills from mapped facets and merge with literal values
1547
+ const reconstructed = reconstructFromMappedFacets(
1548
+ parsed.complexObjectSchema,
1549
+ parsed.complexObject
1550
+ )
1551
+ if (reconstructed !== null) {
1552
+ result[entry.name] = reconstructed
1553
+ continue
1554
+ }
1555
+ }
1556
+ } catch {
1557
+ // Fall through to regular value handling
1558
+ }
1559
+ }
1560
+
1561
+ // Handle regular values (non-inline-script)
1562
+ if (entry.value !== undefined && entry.value !== null) {
1563
+ // Get the type for this entry from the schema
1564
+ const uiType = typeMap?.get(entry.name)
1565
+
1566
+ // Strip type annotation from datapill strings before normalization
1567
+ // ServiceNow stores datapills with type annotations like {{trigger.name|string}}
1568
+ const valueStr = String(entry.value)
1569
+ const cleanedValue = stripDatapillTypeAnnotation(valueStr)
1570
+
1571
+ // Use normalizeInputValue to handle type conversion
1572
+ // This converts:
1573
+ // - "true"/"false" → boolean (when uiType is 'boolean')
1574
+ // - "1"/"0" → true/false (when uiType is 'boolean')
1575
+ // - "123" → number
1576
+ // - JSON strings → parsed objects
1577
+ // - Datapills like "{{trigger.name}}" → kept as string (for later conversion)
1578
+ const normalizedValue = normalizeInputValue(cleanedValue, uiType)
1579
+ result[entry.name] = normalizedValue
1580
+ }
1581
+ }
1582
+
1583
+ return result
1584
+ }
1585
+ }
1586
+
1587
+ /**
1588
+ * Helper function to check if children array contains any datapill values.
1589
+ * Datapills are indicated by {{...}} syntax in the value field.
1590
+ *
1591
+ * @param children - The children array to check
1592
+ * @returns true if any child has a datapill value
1593
+ */
1594
+ export function childrenContainDatapills(children: unknown[]): boolean {
1595
+ for (const child of children) {
1596
+ if (typeof child === 'object' && child !== null) {
1597
+ const childObj = child as { value?: string; children?: unknown[] }
1598
+
1599
+ // Check if this child has a datapill value (pure datapill or template expression)
1600
+ if (childObj.value && typeof childObj.value === 'string') {
1601
+ // Check for pure datapill or template expression containing datapills
1602
+ if (childObj.value.includes('{{') && childObj.value.includes('}}')) {
1603
+ return true
1604
+ }
1605
+ }
1606
+
1607
+ // Recursively check nested children
1608
+ if (childObj.children && Array.isArray(childObj.children) && childObj.children.length > 0) {
1609
+ if (childrenContainDatapills(childObj.children)) {
1610
+ return true
1611
+ }
1612
+ }
1613
+ }
1614
+ }
1615
+ return false
1616
+ }
1617
+
1618
+ // Type guard functions for each shape type
1619
+ export function isConditionalLogicShape(expr: FlowLogicInstanceShape): expr is ConditionalLogicInstanceShape {
1620
+ return [FLOW_LOGIC.IF, FLOW_LOGIC.ELSEIF, FLOW_LOGIC.ELSE].includes(expr.getCallee() as FLOW_LOGIC)
1621
+ }
1622
+
1623
+ export function isForEachShape(expr: FlowLogicInstanceShape): expr is ForEachInstanceShape {
1624
+ return expr.getCallee() === FLOW_LOGIC.FOR_EACH
1625
+ }
1626
+
1627
+ export function isGoBackToShape(expr: FlowLogicInstanceShape): expr is GoBackToInstanceShape {
1628
+ return expr.getCallee() === FLOW_LOGIC.GOBACKTO
1629
+ }
1630
+
1631
+ export function isTryCatchShape(expr: FlowLogicInstanceShape): expr is TryCatchInstanceShape {
1632
+ return expr.getCallee() === FLOW_LOGIC.TRY_CATCH
1633
+ }
1634
+
1635
+ export function isDoInParallelShape(expr: FlowLogicInstanceShape): expr is DoInParallelInstanceShape {
1636
+ return expr.getCallee() === FLOW_LOGIC.DO_IN_PARALLEL
1637
+ }
1638
+
1639
+ export function isSetFlowVariablesShape(expr: FlowLogicInstanceShape): expr is SetFlowVariablesInstanceShape {
1640
+ return expr.getCallee() === FLOW_LOGIC.SET_FLOW_VARIABLES
1641
+ }
1642
+
1643
+ export function isAssignSubflowOutputsShape(expr: FlowLogicInstanceShape): expr is AssignSubflowOutputsInstanceShape {
1644
+ return expr.getCallee() === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS
1645
+ }
1646
+
1647
+ /**
1648
+ * Checks if a shape is a datapill shape (PropertyAccessShape, TemplateExpressionShape, IdentifierShape, PillShape, or PillTemplateShape)
1649
+ */
1650
+ export function isDataPillShape(shape: Shape): boolean {
1651
+ return (
1652
+ shape instanceof PropertyAccessShape ||
1653
+ shape instanceof TemplateExpressionShape ||
1654
+ shape instanceof IdentifierShape ||
1655
+ shape instanceof PillShape ||
1656
+ shape instanceof PillTemplateShape
1657
+ )
1658
+ }
1659
+
1660
+ /**
1661
+ * Recursively resolves datapills in nested values (for FlowObjects/FlowArrays).
1662
+ *
1663
+ * @param value - The value to resolve datapills in
1664
+ * @param transform - Transform context for shape conversion
1665
+ * @returns Resolved value with datapills replaced
1666
+ */
1667
+ async function resolveNestedDataPills(value: unknown, transform: Transform): Promise<unknown> {
1668
+ // Handle datapill shapes
1669
+ if (value instanceof Shape && isDataPillShape(value)) {
1670
+ return await resolveDataPillShape(
1671
+ value as PropertyAccessShape | TemplateExpressionShape | IdentifierShape | PillShape,
1672
+ transform
1673
+ )
1674
+ }
1675
+
1676
+ // Handle ObjectShape - need to resolve its entries
1677
+ if (value instanceof Shape && (value.isObject() || value.is(DurationShape))) {
1678
+ const objectShape = value.isObject()
1679
+ ? value.asObject()
1680
+ : Shape.from(value.getSource(), value.as(DurationShape).getDuration()).asObject()
1681
+ const resolvedObj: { [key: string]: unknown } = {}
1682
+ const entries = objectShape.entries({ resolve: false })
1683
+ for (const [key, shape] of entries) {
1684
+ resolvedObj[key] = await resolveNestedDataPills(shape, transform)
1685
+ }
1686
+ return resolvedObj
1687
+ }
1688
+
1689
+ // Handle ArrayShape - need to resolve its elements
1690
+ if (value instanceof Shape && value.isArray()) {
1691
+ const arrayShape = value.asArray()
1692
+ const resolvedArray: unknown[] = []
1693
+ // IMPORTANT: Use getElements(false) to prevent premature resolution of CallExpressionShapes
1694
+ const elements = arrayShape.getElements(false)
1695
+ for (const element of elements) {
1696
+ if (element === undefined) {
1697
+ continue
1698
+ }
1699
+ resolvedArray.push(await resolveNestedDataPills(element, transform))
1700
+ }
1701
+ return resolvedArray
1702
+ }
1703
+
1704
+ // Handle CallExpressionShape for wfa.dataPill() - convert to pill string
1705
+ if (value instanceof Shape && value.is(CallExpressionShape)) {
1706
+ const callExpr = value.as(CallExpressionShape)
1707
+ if (callExpr.getCallee() === 'wfa.dataPill') {
1708
+ // The first argument is a PropertyAccessShape (e.g., params.inputs.inputString)
1709
+ // The second argument is the type hint (e.g., 'string', 'integer')
1710
+ const firstArg = callExpr.getArgument(0)
1711
+ const secondArg = callExpr.getArgument(1)
1712
+
1713
+ if (firstArg && firstArg.is(PropertyAccessShape)) {
1714
+ // Extract the pill components from PropertyAccessShape
1715
+ const propAccess = firstArg.as(PropertyAccessShape)
1716
+ const elements = propAccess.getElements()
1717
+
1718
+ if (elements.length >= 2) {
1719
+ const propertyNames: string[] = []
1720
+ elements.forEach((element) => {
1721
+ if (element.is(IdentifierShape)) {
1722
+ propertyNames.push(element.as(IdentifierShape).getName())
1723
+ }
1724
+ })
1725
+
1726
+ // Use the same logic as DataPillPlugin to determine root identifier and path
1727
+ const { rootIdentifier, pathStr } = determineRootIdentifierAndPath(
1728
+ propertyNames,
1729
+ // We don't have diagnostics here, casting through unknown to satisfy the type
1730
+ // The function will return null values if this object is used
1731
+ {} as unknown as Diagnostics,
1732
+ propAccess
1733
+ )
1734
+
1735
+ // Get type hint if provided
1736
+ let typePart = ''
1737
+ if (secondArg && secondArg.isString()) {
1738
+ const typeHint = secondArg.asString().getValue()
1739
+ typePart = `|${typeHint}`
1740
+ }
1741
+
1742
+ // Return pill format: {{rootIdentifier.path|type}}
1743
+ if (rootIdentifier) {
1744
+ return pathStr
1745
+ ? `{{${rootIdentifier}.${pathStr}${typePart}}}`
1746
+ : `{{${rootIdentifier}${typePart}}}`
1747
+ }
1748
+ }
1749
+ }
1750
+
1751
+ // If already converted to PillShape or try normal resolution
1752
+ if (firstArg && isDataPillShape(firstArg)) {
1753
+ return await resolveDataPillShape(
1754
+ firstArg as PropertyAccessShape | TemplateExpressionShape | IdentifierShape | PillShape,
1755
+ transform
1756
+ )
1757
+ }
1758
+ }
1759
+ }
1760
+
1761
+ // Extract value from other Shape types
1762
+ if (value instanceof Shape) {
1763
+ value = value.getValue()
1764
+ }
1765
+
1766
+ // Recursively handle arrays
1767
+ if (Array.isArray(value)) {
1768
+ const resolvedArray: unknown[] = []
1769
+ for (const element of value) {
1770
+ // Skip undefined elements
1771
+ if (element === undefined) {
1772
+ continue
1773
+ }
1774
+ resolvedArray.push(await resolveNestedDataPills(element, transform))
1775
+ }
1776
+ return resolvedArray
1777
+ }
1778
+
1779
+ // Recursively handle objects
1780
+ if (typeof value === 'object' && value !== null) {
1781
+ const resolvedObj: { [key: string]: unknown } = {}
1782
+ for (const [key, val] of Object.entries(value)) {
1783
+ // Skip undefined values
1784
+ if (val === undefined) {
1785
+ continue
1786
+ }
1787
+ resolvedObj[key] = await resolveNestedDataPills(val, transform)
1788
+ }
1789
+ return resolvedObj
1790
+ }
1791
+
1792
+ // Return primitive values as-is
1793
+ return value
1794
+ }
1795
+
1796
+ /**
1797
+ * Processes inline scripts in flow logic values (setFlowVariables, assignSubflowOutputs).
1798
+ * Follows the same pattern as checkAndResolveInlineScripts() in flow-instance-plugin.ts.
1799
+ *
1800
+ * @param config - The ObjectShape containing config properties
1801
+ * @returns Array of [key, value] tuples where inline scripts are serialized
1802
+ */
1803
+ function processInlineScriptsInFlowLogic(config: ObjectShape): Array<[string, unknown]> {
1804
+ const entries = config.entries({ resolve: false })
1805
+ const results: [string, unknown][] = []
1806
+
1807
+ for (const [key, shape] of entries) {
1808
+ // Handle wfa.inlineScript() calls
1809
+ if (shape instanceof FDInlineScriptCallShape) {
1810
+ const scriptContent = shape.getScriptContent()
1811
+ // Wrap in TemplateExpressionShape for proper serialization
1812
+ const templateShape = new TemplateExpressionShape({
1813
+ source: shape,
1814
+ literalText: scriptContent,
1815
+ })
1816
+ // Create InlineScriptShape for Flow Designer serialization
1817
+ const inlineScript = new InlineScriptShape({
1818
+ source: templateShape,
1819
+ scriptContent,
1820
+ })
1821
+ // Convert to Flow Designer JSON format
1822
+ const scriptValue = inlineScript.toFlowDesignerJson(key)
1823
+ results.push([key, scriptValue])
1824
+ }
1825
+ }
1826
+
1827
+ return results
1828
+ }
1829
+
1830
+ /**
1831
+ * Resolves datapills and inline scripts in flow logic config properties.
1832
+ * Similar to prepareActionInstanceValueJson() in flow-instance-plugin.ts.
1833
+ *
1834
+ * @param config - The ObjectShape containing config properties
1835
+ * @param transform - Transform context for shape conversion
1836
+ * @param diagnostics - Diagnostics for reporting errors
1837
+ * @returns ObjectShape with resolved datapill and inline script values
1838
+ */
1839
+ async function resolveConfigDataPills(
1840
+ config: ObjectShape,
1841
+ transform: Transform,
1842
+ diagnostics: Diagnostics
1843
+ ): Promise<ObjectShape> {
1844
+ const entries = config.entries({ resolve: false })
1845
+ const dataPillProps: { [key: string]: unknown } = {}
1846
+
1847
+ // Step 1: Resolve datapills (including nested datapills in FlowObject/FlowArray values)
1848
+ for (const [key, shape] of entries) {
1849
+ // Validate: annotation field must not contain datapills or inline scripts
1850
+ if (key === 'annotation' && (isDataPillShape(shape) || shape instanceof FDInlineScriptCallShape)) {
1851
+ diagnostics.error(
1852
+ shape,
1853
+ 'Datapills and inline scripts are not allowed in the annotation field. Please use a static string value.'
1854
+ )
1855
+ // Still extract the value to continue processing
1856
+ dataPillProps[key] = shape.getValue()
1857
+ continue
1858
+ }
1859
+
1860
+ if (isDataPillShape(shape)) {
1861
+ dataPillProps[key] = await resolveDataPillShape(
1862
+ shape as PropertyAccessShape | TemplateExpressionShape | IdentifierShape | PillShape,
1863
+ transform
1864
+ )
1865
+ } else {
1866
+ // Recursively resolve nested datapills in FlowObject/FlowArray values
1867
+ dataPillProps[key] = await resolveNestedDataPills(shape, transform)
1868
+ }
1869
+ }
1870
+
1871
+ // Step 2: Process inline scripts (wfa.inlineScript() calls)
1872
+ // This serializes inline scripts into Flow Designer JSON format with scriptActive: true
1873
+ const inlineScriptResults = processInlineScriptsInFlowLogic(config)
1874
+ const inlineScriptMap = new Map(inlineScriptResults)
1875
+
1876
+ // Step 3: Replace raw inline script strings with their serialized JSON objects
1877
+ // For fields with inline scripts: replace the raw string from Step 1 with the properly
1878
+ // serialized Flow Designer JSON object from Step 2. All other fields remain unchanged.
1879
+ const mergedProps: { [key: string]: unknown } = {}
1880
+ for (const [key, value] of Object.entries(dataPillProps)) {
1881
+ mergedProps[key] = inlineScriptMap.has(key) ? inlineScriptMap.get(key) : value
1882
+ }
1883
+
1884
+ return Shape.from(config.getSource(), mergedProps).asObject()
1885
+ }
1886
+
1887
+ /**
1888
+ * Extracts the appropriate data from different flow logic shape types.
1889
+ * Resolves datapills in config properties for If, ElseIf, Else, ForEach, and WaitForADuration.
1890
+ */
1891
+ async function extractDataFromShape(
1892
+ expr: FlowLogicInstanceShape,
1893
+ transform: Transform,
1894
+ diagnostics: Diagnostics
1895
+ ): Promise<ObjectShape | Record | StringShape> {
1896
+ // Use a more elegant approach with early returns for each shape type
1897
+ if (isGoBackToShape(expr)) {
1898
+ return expr.getStep()
1899
+ }
1900
+
1901
+ if (isSetFlowVariablesShape(expr)) {
1902
+ const variablesToSet = expr.getVariablesToSet()
1903
+ return await resolveConfigDataPills(variablesToSet, transform, diagnostics)
1904
+ }
1905
+
1906
+ if (isAssignSubflowOutputsShape(expr)) {
1907
+ const assignedSubflowOutputs = expr.getAssignedSubflowOutputs()
1908
+ return await resolveConfigDataPills(assignedSubflowOutputs, transform, diagnostics)
1909
+ }
1910
+
1911
+ if (isForEachShape(expr)) {
1912
+ // Check if argument 0 (items) is a PropertyAccessShape before processing
1913
+ const itemsArg = expr.getArgument(0, false)
1914
+ if (itemsArg?.is(PropertyAccessShape)) {
1915
+ const items = expr.getItems()
1916
+ const itemString = await resolveDataPillShape(items, transform)
1917
+ return Shape.from(expr, itemString).asString()
1918
+ }
1919
+ // If not a PropertyAccessShape (e.g., StringLiteralShape when no data pill assigned),
1920
+ // return empty string instead of falling through to default case (which returns ObjectShape)
1921
+ return Shape.from(expr, '').asString()
1922
+ }
1923
+
1924
+ // For conditional logic (If/ElseIf/Else) and WAIT_FOR_A_DURATION, resolve datapills in config
1925
+ const logicType = expr.getCallee() as FLOW_LOGIC
1926
+ if (isConditionalLogicShape(expr) || logicType === FLOW_LOGIC.WAIT_FOR_A_DURATION) {
1927
+ const config = expr.getConfig()
1928
+ return await resolveConfigDataPills(config, transform, diagnostics)
1929
+ }
1930
+
1931
+ // Default case: extract config as-is for other shape types
1932
+ return expr.getConfig()
1933
+ }
1934
+
1935
+ /**
1936
+ * Main function to process flow logic instances and build records
1937
+ */
1938
+ export async function getFlowLogicInstanceRecord(
1939
+ expr: FlowLogicInstanceShape,
1940
+ context: { diagnostics: Diagnostics; transform: Transform; factory: Factory }
1941
+ ): Promise<Result<Record>> {
1942
+ const { diagnostics, transform, factory } = context
1943
+ const logicType = expr.getCallee() as FLOW_LOGIC
1944
+
1945
+ // Validate flow logic context
1946
+ const diagnosticError = validateFlowLogic(expr, diagnostics)
1947
+ if (diagnosticError) {
1948
+ diagnostics.error(expr, diagnosticError)
1949
+ return { success: false }
1950
+ }
1951
+
1952
+ // Extract data based on shape type using type-specific extractors
1953
+ const data = await extractDataFromShape(expr, transform, diagnostics)
1954
+ // Note: childName for arrays is now handled in resolveComplexInput within postProcess functions
1955
+ const values = FlowLogicValueProcessor.prepare(logicType, data)
1956
+ let instanceRecord: Record = await buildFlowLogicInstanceRecord(expr, factory, values)
1957
+
1958
+ if (isAssignSubflowOutputsShape(expr)) {
1959
+ instanceRecord = instanceRecord.merge({
1960
+ outputs_assigned: expr.getAssignedSubflowOutputsAsString(),
1961
+ })
1962
+ }
1963
+
1964
+ if (isSetFlowVariablesShape(expr)) {
1965
+ instanceRecord = instanceRecord.merge({
1966
+ flow_variables_assigned: expr.getVariablesToSetAsString(),
1967
+ })
1968
+ }
1969
+
1970
+ instanceRecord = instanceRecord.merge({
1971
+ values: values,
1972
+ })
1973
+ // Handle sibling connections for conditional logic types
1974
+ if (requiresSiblingValidation(logicType)) {
1975
+ const siblingResult = await handleSiblingConnection(expr, transform, diagnostics)
1976
+ if (!siblingResult.success) {
1977
+ return siblingResult
1978
+ }
1979
+ if (siblingResult.value.connectedTo) {
1980
+ instanceRecord = instanceRecord.merge({
1981
+ connected_to: siblingResult.value.connectedTo,
1982
+ values: values,
1983
+ })
1984
+ }
1985
+ }
1986
+
1987
+ return { success: true, value: instanceRecord }
1988
+ }
1989
+
1990
+ function requiresSiblingValidation(logicType: FLOW_LOGIC): boolean {
1991
+ return [FLOW_LOGIC.IF, FLOW_LOGIC.ELSEIF, FLOW_LOGIC.ELSE].includes(logicType)
1992
+ }
1993
+
1994
+ async function handleSiblingConnection(
1995
+ expr: FlowLogicInstanceShape,
1996
+ transform: Transform,
1997
+ diagnostics: Diagnostics
1998
+ ): Promise<Result<{ connectedTo?: string }>> {
1999
+ const sibling = expr
2000
+ .getOriginalNode()
2001
+ .getParent()
2002
+ ?.getPreviousSibling((node) => !ts.Node.isCommentStatement(node))
2003
+ ?.asKind(ts.SyntaxKind.ExpressionStatement)
2004
+ ?.getExpression()
2005
+ .asKind(ts.SyntaxKind.CallExpression)
2006
+
2007
+ const siblingError = validateSibling(expr, sibling)
2008
+ if (siblingError) {
2009
+ diagnostics.error(expr, siblingError)
2010
+ return { success: false }
2011
+ }
2012
+
2013
+ const logicType = expr.getCallee() as FLOW_LOGIC
2014
+ if (sibling && (logicType === FLOW_LOGIC.ELSE || logicType === FLOW_LOGIC.ELSEIF)) {
2015
+ const siblingRecord = await transform.toRecord(sibling)
2016
+ if (siblingRecord.success) {
2017
+ return { success: true, value: { connectedTo: siblingRecord.value.get('ui_id').asString().getValue() } }
2018
+ }
2019
+ }
2020
+
2021
+ return { success: true, value: {} }
2022
+ }
2023
+
2024
+ type FlowLogicArgumentBuilder = (record: Record, logicBodyShapes: Shape[], database: Database) => Shape<unknown>[]
2025
+
2026
+ const FLOW_LOGIC_ARGUMENT_BUILDERS: { [K in FLOW_LOGIC]: FlowLogicArgumentBuilder } = {
2027
+ [FLOW_LOGIC.GOBACKTO]: (record, _, database) => {
2028
+ const stepId = FlowLogicValueProcessor.parse(
2029
+ FLOW_LOGIC.GOBACKTO,
2030
+ record.get('values').asString().getValue()
2031
+ ) as string
2032
+
2033
+ const stepRecord =
2034
+ database.get('sys_hub_action_instance_v2', { ui_id: stepId, flow: record.get('flow').getValue() }) ||
2035
+ database.get('sys_hub_sub_flow_instance_v2', { ui_id: stepId, flow: record.get('flow').getValue() }) ||
2036
+ database.get('sys_hub_flow_logic_instance_v2', { ui_id: stepId, flow: record.get('flow').getValue() })
2037
+
2038
+ if (!stepRecord) {
2039
+ throw new Error(`Step with id ${stepId} not found`)
2040
+ }
2041
+
2042
+ // Target step validation is now handled during FlowLogicInstanceShape.toRecord phase
2043
+ // No validation needed here anymore
2044
+
2045
+ const firstArg = new ObjectShape({
2046
+ source: record,
2047
+ properties: {
2048
+ annotation: record.get('comment').asString().getValue(),
2049
+ $id: NowIdShape.from(record),
2050
+ },
2051
+ })
2052
+
2053
+ const source = stepRecord.getSource()
2054
+ if (source instanceof CallExpressionShape) {
2055
+ const identifier = stepRecord
2056
+ .getOriginalNode()
2057
+ ?.getFirstAncestorByKind(ts.SyntaxKind.VariableDeclaration)
2058
+ ?.getNameNode()
2059
+
2060
+ if (identifier) {
2061
+ return [
2062
+ firstArg,
2063
+ new IdentifierShape({
2064
+ source: record,
2065
+ name: identifier.getText(),
2066
+ }),
2067
+ ]
2068
+ }
2069
+ }
2070
+
2071
+ return [firstArg, Shape.from(record, stepId)]
2072
+ },
2073
+
2074
+ [FLOW_LOGIC.IF]: buildConditionalArguments,
2075
+ [FLOW_LOGIC.ELSEIF]: buildConditionalArguments,
2076
+ [FLOW_LOGIC.ELSE]: buildConditionalArguments,
2077
+ [FLOW_LOGIC.ENDFLOW]: buildBasicArguments,
2078
+ [FLOW_LOGIC.EXITLOOP]: buildBasicArguments,
2079
+ [FLOW_LOGIC.SKIP_ITERATION]: buildBasicArguments,
2080
+ [FLOW_LOGIC.WAIT_FOR_A_DURATION]: buildWaitForADurationArguments,
2081
+ [FLOW_LOGIC.FOR_EACH]: buildForEachArguments,
2082
+ [FLOW_LOGIC.DO_IN_PARALLEL]: buildBasicArguments,
2083
+ [FLOW_LOGIC.TRY_CATCH]: buildBasicArguments,
2084
+ [FLOW_LOGIC.SET_FLOW_VARIABLES]: buildSetFlowVariablesArguments,
2085
+ [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: buildAssignSubflowOutputsArguments,
2086
+ }
2087
+
2088
+ /**
2089
+ * Generic helper to build arguments for flow logic operations that follow the pattern:
2090
+ * (config, schema, values)
2091
+ *
2092
+ * This pattern is used by SetFlowVariables and AssignSubflowOutputs.
2093
+ *
2094
+ * @param record - ServiceNow sys_hub_flow_logic_instance_v2 record
2095
+ * @param database - Database to query for type information
2096
+ * @param tableName - Table name to query for schema records (e.g., 'sys_hub_flow_variable', 'sys_hub_flow_output')
2097
+ * @param flowLogicType - Flow logic type for parsing (e.g., FLOW_LOGIC.SET_FLOW_VARIABLES)
2098
+ * @param schemaRef - Schema reference string (e.g., '{{flowVariables}}', '{{outputs}}')
2099
+ * @returns Array of three Shape arguments
2100
+ */
2101
+ function buildVariableOrOutputArguments(
2102
+ record: Record,
2103
+ database: Database,
2104
+ tableName: string,
2105
+ flowLogicType: FLOW_LOGIC,
2106
+ schemaRef: string
2107
+ ): Shape<unknown>[] {
2108
+ const comment = record.get('comment').asString().getValue()
2109
+ const valuesString = record.get('values').asString().getValue()
2110
+ const flowId = record.get('flow').asString().getValue()
2111
+
2112
+ // Get flow definition and extract type information from schema
2113
+ const typeMap = new Map<string, string>()
2114
+ let labelCacheMap: Map<string, string> | undefined
2115
+ const flowRecord = database.get('sys_hub_flow', flowId)
2116
+
2117
+ if (flowRecord) {
2118
+ // Get the schema records that belong to this flow
2119
+ const schemaRecords = database.query(tableName).filter((v) => v.get('model').getValue() === flowId)
2120
+
2121
+ for (const schemaRecord of schemaRecords) {
2122
+ const name = schemaRecord.get('element').asString().getValue()
2123
+ const internalType = schemaRecord.get('internal_type').asString().getValue()
2124
+ typeMap.set(name, internalType)
2125
+ }
2126
+
2127
+ // Extract label cache from flow record for datapill type preservation
2128
+ try {
2129
+ const labelCacheValue = flowRecord.get('label_cache')?.getValue()
2130
+ if (labelCacheValue && typeof labelCacheValue === 'string') {
2131
+ // Parse label cache JSON and create name->type map
2132
+ const labelCacheEntries = JSON.parse(labelCacheValue) as Array<{ name: string; type: string }>
2133
+ labelCacheMap = new Map<string, string>()
2134
+ for (const entry of labelCacheEntries) {
2135
+ if (entry.name && entry.type) {
2136
+ labelCacheMap.set(entry.name, entry.type)
2137
+ }
2138
+ }
2139
+ }
2140
+ } catch {
2141
+ // If label cache extraction fails, continue without it
2142
+ labelCacheMap = undefined
2143
+ }
2144
+ }
2145
+
2146
+ // Parse the values from the gzipped string with type conversion
2147
+ // Returns object with properly typed values, inline script markers, or datapill strings
2148
+ const parsedData = FlowLogicValueProcessor.parse(flowLogicType, valuesString, typeMap, labelCacheMap) as {
2149
+ [key: string]: unknown
2150
+ }
2151
+
2152
+ // Build the config object (first argument)
2153
+ const configArg = new ObjectShape({
2154
+ source: record,
2155
+ properties: {
2156
+ ...(comment ? { annotation: comment } : {}),
2157
+ $id: NowIdShape.from(record),
2158
+ },
2159
+ })
2160
+
2161
+ // Second argument: schema reference ({{flowVariables}} or {{outputs}})
2162
+ // Keep as string - will be transformed to PropertyAccessShape by datapill transformer
2163
+ // The transformer will use the actual parameter name from the arrow function context
2164
+ const schemaArg = Shape.from(record, schemaRef)
2165
+
2166
+ // Build the values object (third argument)
2167
+ // Process inline scripts and create FDInlineScriptCallShape (matching action plugin pattern)
2168
+ const valueProperties: { [key: string]: Shape } = {}
2169
+ for (const [key, value] of Object.entries(parsedData)) {
2170
+ // Check for inline script (has scriptActive and scriptContent from XML)
2171
+ if (value && typeof value === 'object' && 'scriptActive' in value && 'scriptContent' in value) {
2172
+ // Create FDInlineScriptCallShape for inline scripts (same as actions)
2173
+ const script = value as { scriptActive: boolean; scriptContent: string }
2174
+ valueProperties[key] = new FDInlineScriptCallShape({
2175
+ source: record,
2176
+ scriptContent: script.scriptContent,
2177
+ })
2178
+ } else {
2179
+ // Primitive value or string - convert using Shape.from()
2180
+ valueProperties[key] = Shape.from(record, value)
2181
+ }
2182
+ }
2183
+
2184
+ const valuesArg = new ObjectShape({
2185
+ source: record,
2186
+ properties: valueProperties,
2187
+ })
2188
+
2189
+ return [configArg, schemaArg, valuesArg]
2190
+ }
2191
+
2192
+ /**
2193
+ * Builds arguments for SetFlowVariables when converting XML to Fluent.
2194
+ * Creates the three arguments required by the SetFlowVariables API:
2195
+ * 1. Config object with $id and optional annotation
2196
+ * 2. Schema reference (placeholder for params.flowVariables)
2197
+ * 3. Values object with variable assignments
2198
+ *
2199
+ * **Type Conversion Pipeline:**
2200
+ * XML (gzipped) → parse() → normalizeInputValue(with type) → typed values → Shape.from() → Shapes
2201
+ *
2202
+ * **Example:**
2203
+ * ```typescript
2204
+ * // XML: { variables: [{ name: "count", value: "123" }, { name: "active", value: "1" }] }
2205
+ * // Result: SetFlowVariables({ $id: ... }, params.flowVariables, { count: 123, active: true })
2206
+ * ```
2207
+ *
2208
+ * @param record - ServiceNow sys_hub_flow_logic_instance_v2 record
2209
+ * @param _logicBodyShapes - Unused parameter (required by FlowLogicArgumentBuilder interface)
2210
+ * @param database - Database to query for flow variable schemas
2211
+ * @returns Array of three Shape arguments for SetFlowVariables call
2212
+ */
2213
+ function buildSetFlowVariablesArguments(
2214
+ record: Record,
2215
+ _logicBodyShapes: Shape[],
2216
+ database: Database
2217
+ ): Shape<unknown>[] {
2218
+ return buildVariableOrOutputArguments(
2219
+ record,
2220
+ database,
2221
+ 'sys_hub_flow_variable',
2222
+ FLOW_LOGIC.SET_FLOW_VARIABLES,
2223
+ '{{flowVariables}}'
2224
+ )
2225
+ }
2226
+
2227
+ /**
2228
+ * Builds arguments for AssignSubflowOutputs when converting XML to Fluent.
2229
+ * Creates the three arguments required by the AssignSubflowOutputs API:
2230
+ * 1. Config object with $id and optional annotation
2231
+ * 2. Schema reference (placeholder for params.outputs or subflow outputs schema)
2232
+ * 3. Values object with output assignments
2233
+ *
2234
+ * **Type Conversion Pipeline:**
2235
+ * XML (gzipped) → parse() → normalizeInputValue(with type) → typed values → Shape.from() → Shapes
2236
+ *
2237
+ * **Example:**
2238
+ * ```typescript
2239
+ * // XML: { outputsToAssign: [{ name: "result", value: "success" }, { name: "isComplete", value: "1" }] }
2240
+ * // Result: AssignSubflowOutputs({ $id: ... }, params.outputs, { result: "success", isComplete: true })
2241
+ * ```
2242
+ *
2243
+ * @param record - ServiceNow sys_hub_flow_logic_instance_v2 record
2244
+ * @param _logicBodyShapes - Unused parameter (required by FlowLogicArgumentBuilder interface)
2245
+ * @param database - Database to query for subflow output schemas
2246
+ * @returns Array of three Shape arguments for AssignSubflowOutputs call
2247
+ */
2248
+ function buildAssignSubflowOutputsArguments(
2249
+ record: Record,
2250
+ _logicBodyShapes: Shape[],
2251
+ database: Database
2252
+ ): Shape<unknown>[] {
2253
+ return buildVariableOrOutputArguments(
2254
+ record,
2255
+ database,
2256
+ 'sys_hub_flow_output',
2257
+ FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS,
2258
+ '{{outputs}}'
2259
+ )
2260
+ }
2261
+
2262
+ function buildBasicArguments(record: Record): Shape<unknown>[] {
2263
+ return [
2264
+ new ObjectShape({
2265
+ source: record,
2266
+ properties: {
2267
+ annotation: record.get('comment').asString().getValue(),
2268
+ $id: NowIdShape.from(record),
2269
+ },
2270
+ }),
2271
+ ]
2272
+ }
2273
+
2274
+ function buildWaitForADurationArguments(
2275
+ record: Record,
2276
+ _logicBodyShapes: Shape[],
2277
+ database: Database
2278
+ ): Shape<unknown>[] {
2279
+ const zippedInputs = record.get('values').asString().getValue()
2280
+ const logicDefinitionId = record.get('logic_definition').asString().getValue()
2281
+ const logicDefinition = database.get('sys_hub_flow_logic_definition', logicDefinitionId)
2282
+
2283
+ const definitionInputs = logicDefinition
2284
+ ?.flat()
2285
+ .filter((v): v is Record => v.is(Record) && v.getTable() === 'sys_hub_flow_logic_input')
2286
+
2287
+ const inputsShape = buildInputsShapeFromZipped({
2288
+ zippedInputs,
2289
+ definitionInputs,
2290
+ record,
2291
+ })
2292
+
2293
+ const comment = record.get('comment').asString().getValue()
2294
+ const uuid = record.get('ui_id').asString().getValue()
2295
+
2296
+ // Get properties from inputsShape (which is now an ObjectShape)
2297
+ // Use entries({ resolve: false }) to preserve shape types (DurationShape, etc.)
2298
+ const inputProperties = inputsShape ? Object.fromEntries(inputsShape.entries({ resolve: false })) : {}
2299
+
2300
+ const mergedProperties = {
2301
+ ...(comment ? { annotation: comment } : {}),
2302
+ $id: NowIdShape.from(record),
2303
+ ...(uuid ? { uuid } : {}),
2304
+ ...inputProperties,
2305
+ }
2306
+
2307
+ const finalArg = new ObjectShape({
2308
+ source: record,
2309
+ properties: mergedProperties,
2310
+ })
2311
+
2312
+ return [finalArg]
2313
+ }
2314
+
2315
+ function buildConditionalArguments(record: Record, logicBodyShapes: Shape[]): Shape<unknown>[] {
2316
+ const logicName = FlowLogicSysId.getLogicName(record.get('logic_definition').asString().getValue()) as FLOW_LOGIC
2317
+ const valuesJson = FlowLogicValueProcessor.parse(logicName, record.get('values').asString().getValue()) as {
2318
+ condition?: string
2319
+ label?: string
2320
+ }
2321
+
2322
+ const properties = {
2323
+ ...valuesJson,
2324
+ annotation: record.get('comment').asString().getValue(),
2325
+ $id: NowIdShape.from(record),
2326
+ }
2327
+
2328
+ const firstArg = new ObjectShape({ source: record, properties })
2329
+ return [
2330
+ firstArg,
2331
+ new ArrowFunctionShape({
2332
+ source: record,
2333
+ parameters: [],
2334
+ statements: logicBodyShapes,
2335
+ }),
2336
+ ]
2337
+ }
2338
+
2339
+ /**
2340
+ * Builds arguments for ForEach flow logic from XML record.
2341
+ * Converts XML structure back to Fluent ForEach syntax.
2342
+ *
2343
+ * @param record - The sys_hub_flow_logic_instance_v2 record
2344
+ * @param logicBodyShapes - The child shapes that form the loop body
2345
+ * @returns Array of shapes: [items, config, body]
2346
+ *
2347
+ * @example
2348
+ * // XML with values: { items: "{{trigger.array_field}}" }
2349
+ * // Converts to: ForEach(params.trigger.array_field, { $id, annotation }, (item) => {...})
2350
+ */
2351
+ /**
2352
+ * Build arguments for forEach flow logic with intelligent parameter name preservation.
2353
+ *
2354
+ * PARAMETER NAMING STRATEGY (Priority Order):
2355
+ *
2356
+ * 1. **Original Source Extraction** (Round-Trip Preservation):
2357
+ * - For Fluent-authored flows, extracts parameter name from original AST
2358
+ * - Preserves user-chosen names: (dept), (category), (item_0), etc.
2359
+ * - Enables lossless round-trips: Fluent → XML → Fluent
2360
+ *
2361
+ * 2. **Order-Based Naming** (UI-Authored Flows):
2362
+ * - For UI-authored flows (no original source), generates item_${order}
2363
+ * - Creates predictable names: item_0, item_1, item_2
2364
+ * - Disambiguates nested forEach loops clearly
2365
+ *
2366
+ * 3. **Default Fallback** (Edge Cases):
2367
+ * - Falls back to 'item' if neither strategy succeeds
2368
+ *
2369
+ * @example Round-Trip Preservation
2370
+ * // Original Fluent code:
2371
+ * forEach(users, {...}, (dept) => {
2372
+ * forEach(categories, {...}, (category) => {
2373
+ * action(..., { msg: `${dept.name} - ${category.value}` })
2374
+ * })
2375
+ * })
2376
+ *
2377
+ * // After deploy + pull: PRESERVED!
2378
+ * forEach(users, {...}, (dept) => { // ✅ 'dept' preserved
2379
+ * forEach(categories, {...}, (category) => { // ✅ 'category' preserved
2380
+ * action(..., { msg: `${dept.name} - ${category.value}` })
2381
+ * })
2382
+ * })
2383
+ *
2384
+ * @example UI-Authored Flow
2385
+ * // Flow created in UI, pulled to Fluent:
2386
+ * forEach(users, {...}, (item_0) => { // Generated from order=0
2387
+ * forEach(categories, {...}, (item_3) => { // Generated from order=3
2388
+ * action(..., { msg: `${item_0.name} - ${item_3.value}` })
2389
+ * })
2390
+ * })
2391
+ *
2392
+ * @param record - The forEach flow logic instance record
2393
+ * @param logicBodyShapes - The shapes within the forEach body
2394
+ * @returns Array of shapes [items, config, body] for forEach arguments
2395
+ */
2396
+
2397
+ /**
2398
+ * Extracts the forEach parameter name from the original source (for round-trip preservation).
2399
+ * Falls back to order-based naming for UI-authored flows, or 'item' as final fallback.
2400
+ *
2401
+ * @param record - The forEach flow logic record
2402
+ * @returns The parameter name to use in the arrow function
2403
+ */
2404
+ function extractForEachParameterName(record: Record): string {
2405
+ try {
2406
+ const originalSource = record.getOriginalSource()
2407
+
2408
+ // Check if we have a valid ts.Node
2409
+ if (!ts.Node.isNode(originalSource)) {
2410
+ return getFallbackParameterName(record)
2411
+ }
2412
+
2413
+ // For forEach: wfa.flowLogic.forEach(items, config, (param) => {...})
2414
+ // Third argument (index 2) is the arrow function
2415
+ const arrowFn = (originalSource as ts.CallExpression).getArguments()[2]?.asKind(ts.SyntaxKind.ArrowFunction)
2416
+
2417
+ if (!arrowFn) {
2418
+ return getFallbackParameterName(record)
2419
+ }
2420
+
2421
+ const params = arrowFn.getParameters()
2422
+ const paramName = params[0]?.getName()
2423
+
2424
+ return paramName || getFallbackParameterName(record)
2425
+ } catch {
2426
+ // Failed to extract parameter name - using fallback
2427
+ return getFallbackParameterName(record)
2428
+ }
2429
+ }
2430
+
2431
+ /**
2432
+ * Gets the fallback parameter name based on order or default.
2433
+ */
2434
+ function getFallbackParameterName(record: Record): string {
2435
+ const order = record.get('order')?.getValue() as string | undefined
2436
+ return order ? `item_${order}` : 'item'
2437
+ }
2438
+
2439
+ function buildForEachArguments(record: Record, logicBodyShapes: Shape[]): Shape<unknown>[] {
2440
+ // 1. Parse values field to extract items data pill string
2441
+ const logicName = FlowLogicSysId.getLogicName(record.get('logic_definition').asString().getValue()) as FLOW_LOGIC
2442
+
2443
+ const valuesJson = FlowLogicValueProcessor.parse(logicName, record.get('values').asString().getValue()) as {
2444
+ items?: string
2445
+ }
2446
+
2447
+ // 2. Return items as raw string - will be converted by processInstanceForDatapills
2448
+ // with the correct parameter name extracted from the flow
2449
+ const itemsString = valuesJson.items || ''
2450
+ const itemsShape = Shape.from(record, itemsString)
2451
+
2452
+ // 3. Build config ObjectShape
2453
+ const configShape = new ObjectShape({
2454
+ source: record,
2455
+ properties: {
2456
+ annotation: record.get('comment').asString().getValue(),
2457
+ $id: NowIdShape.from(record),
2458
+ },
2459
+ })
2460
+
2461
+ // 4. Build body ArrowFunctionShape with parameter name extraction
2462
+ // Strategy for parameter naming (in priority order):
2463
+ // 1. Extract from original source (for round-trip preservation) - e.g., user wrote (item_0), (dept), etc.
2464
+ // 2. Use order-based naming for UI-authored flows - e.g., item_0, item_1, item_2
2465
+ // 3. Fall back to 'item' if order is unavailable
2466
+
2467
+ const parameterName = extractForEachParameterName(record)
2468
+
2469
+ const bodyShape = new ArrowFunctionShape({
2470
+ source: record,
2471
+ parameters: [new IdentifierShape({ source: record, name: parameterName })],
2472
+ statements: logicBodyShapes,
2473
+ })
2474
+
2475
+ // 5. Return in correct order: [items, config, body]
2476
+ return [itemsShape, configShape, bodyShape]
2477
+ }
2478
+
2479
+ function buildInputsShapeFromZipped({
2480
+ zippedInputs,
2481
+ definitionInputs,
2482
+ record,
2483
+ }: {
2484
+ zippedInputs: string
2485
+ definitionInputs: Record[] | undefined
2486
+ record: Record
2487
+ }): ObjectShape | undefined {
2488
+ if (!zippedInputs) {
2489
+ return undefined
2490
+ }
2491
+
2492
+ // Type definition includes parameter field which contains type information from XML
2493
+ let inputsJson: {
2494
+ inputs: Array<{
2495
+ name: string
2496
+ value: string
2497
+ parameter?: { type?: string; [key: string]: unknown }
2498
+ }>
2499
+ }
2500
+ try {
2501
+ const inputsBuffer = gunzipSync(Buffer.from(zippedInputs, 'base64'))
2502
+ inputsJson = JSON.parse(inputsBuffer.toString('utf8'))
2503
+ } catch {
2504
+ // Failed to parse zipped inputs - returning undefined
2505
+ return undefined
2506
+ }
2507
+
2508
+ const inputs: { [key: string]: unknown } = {}
2509
+ if (inputsJson.inputs) {
2510
+ const durationTypeInput = inputsJson.inputs.find((i) => i.name === 'duration_type')
2511
+ const durationType = (durationTypeInput ? normalizeInputValue(durationTypeInput.value) : '') as string
2512
+ const validProps = DURATION_TYPE_PROPS[durationType]
2513
+
2514
+ if (validProps) {
2515
+ for (const input of inputsJson.inputs) {
2516
+ const mappedName = NAME_MAP[input.name] ?? input.name
2517
+
2518
+ if (validProps.has(mappedName)) {
2519
+ const defInput = definitionInputs?.find((def) => def.get('element').getValue() === input.name)
2520
+ const uiType = defInput?.get('ui_type')?.asString()?.getValue()
2521
+
2522
+ // Use uiType or fallback to parameter.type (consistent with flow-instance-plugin)
2523
+ const effectiveType = uiType ?? input.parameter?.type
2524
+ const value = normalizeInputValue(input.value, effectiveType, record)
2525
+
2526
+ if (value !== '') {
2527
+ inputs[mappedName] = value
2528
+ }
2529
+ }
2530
+ }
2531
+ }
2532
+ }
2533
+
2534
+ return Shape.from(record, inputs).asObject()
2535
+ }
2536
+
2537
+ export function getFlowLogicArguments(
2538
+ logicName: FLOW_LOGIC,
2539
+ record: Record,
2540
+ logicBodyShapes: Shape[],
2541
+ database: Database
2542
+ ): Shape<unknown>[] {
2543
+ const builder = FLOW_LOGIC_ARGUMENT_BUILDERS[logicName]
2544
+
2545
+ if (!builder) {
2546
+ throw new Error(`Unsupported flow logic type for argument building: ${logicName}`)
2547
+ }
2548
+
2549
+ return builder(record, logicBodyShapes, database)
2550
+ }