@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,2173 @@
1
+ import {
2
+ CallExpressionShape,
3
+ Plugin,
4
+ UndefinedShape,
5
+ Record,
6
+ type Relationship,
7
+ type Relationships,
8
+ IdentifierShape,
9
+ ObjectShape,
10
+ VariableStatementShape,
11
+ StringShape,
12
+ PropertyAccessShape,
13
+ type Shape,
14
+ TemplateExpressionShape,
15
+ TemplateSpanShape,
16
+ TemplateValueShape,
17
+ type Diagnostics,
18
+ ts,
19
+ deleteMultipleDiff,
20
+ StringLiteralShape,
21
+ type Logger,
22
+ } from '@servicenow/sdk-build-core'
23
+ import { gzipSync } from 'node:zlib'
24
+
25
+ import { ArrowFunctionShape } from '../../arrow-function-plugin'
26
+ import { NowIdShape } from '../../now-id-plugin'
27
+ import { buildVariableShapes, complexObjectMatchesIoRecord } from '../utils/flow-io-to-record'
28
+ import { FLOW_API_NAME, slugifyString, SUBFLOW_API_NAME, TRIGGER_INSTANCE_API_NAME } from '../utils/flow-constants'
29
+ import { generateXML } from '../utils/flow-to-xml'
30
+ import { DO_IN_PARALLEL_BLOCK_SYS_ID, FLOW_LOGIC, FlowLogicSysId } from '../flow-logic/flow-logic-constants'
31
+ import { TriggerInstancePlugin } from './flow-trigger-instance-plugin'
32
+ import { FlowLogicPlugin } from '../flow-logic/flow-logic-plugin'
33
+ import { FlowInstancePlugin } from './flow-instance-plugin'
34
+ import {
35
+ checkForUnsupportedFlowDescendants,
36
+ getIdentifierFromRecord,
37
+ getRecordFromFlowInstaceShape,
38
+ uuidToSysId,
39
+ validateFlowVariableCall,
40
+ } from '../utils/utils'
41
+ import { getCallExpressionName } from '../../utils'
42
+ import {
43
+ extractLabelCache,
44
+ createDefinitionMap,
45
+ extractTriggerInputs,
46
+ extractTriggerOutputs,
47
+ updateForEachMetadata,
48
+ extractDataPillsFromValuesArray,
49
+ } from '../utils/label-cache-processor'
50
+ import { createLableCacheNameToTypeMap } from '../utils/label-cache-parser'
51
+ import {
52
+ parseSinglePill,
53
+ isUuidPrefix,
54
+ mapPillPrefixToTsRoot,
55
+ createPropertyAccessFromPath,
56
+ } from '../utils/pill-string-parser'
57
+ import { isDataPill } from '../utils/complex-object-resolver'
58
+ import { ArrayShape } from '@servicenow/sdk-build-core'
59
+ import { processFlowInputs, processFlowOutputs, processFlowVariables } from '../utils/flow-variable-processor'
60
+ import { buildUuidToIdentifierMap, processInstanceForDatapills, uuidToRecordMap } from '../utils/datapill-transformer'
61
+ import {
62
+ DEFAULT_PARAM_NAME,
63
+ PARALLEL_ORDER_SEPARATOR,
64
+ FLOW_PRIORITY_DEFAULT,
65
+ RUN_AS_DEFAULT,
66
+ ACCESS_DEFAULT,
67
+ GENERATION_SOURCE,
68
+ EMPTY_STRING,
69
+ BOOLEAN_FALSE_STRING,
70
+ FLOW_STATUS_DRAFT,
71
+ FLOW_VERSION,
72
+ FLOW_TYPE_FLOW,
73
+ FLOW_TYPE_SUBFLOW,
74
+ } from '../constants/flow-plugin-constants'
75
+ import { resolveComplexInput } from '../utils/complex-object-resolver'
76
+
77
+ /** Field name for complex object collection arrays in JSON payload */
78
+ const COMPLEX_OBJECT_COLLECTION_FIELD = '$COCollectionField'
79
+
80
+ // ============================================================================
81
+ // TYPE DEFINITIONS FOR COMPLEX OBJECTS
82
+ // ============================================================================
83
+
84
+ /** Represents a field facet map with UI metadata */
85
+ interface FieldFacetMap {
86
+ choiceOption?: string
87
+ uiTypeLabel?: string
88
+ mapped?: string
89
+ }
90
+
91
+ /** Represents a parameter object for a flow variable or field */
92
+ interface ParameterObject {
93
+ children: ChildParameter[]
94
+ type_label: string
95
+ id: string
96
+ label: string
97
+ name: string
98
+ type: string
99
+ order: number
100
+ extended: boolean
101
+ mandatory: boolean
102
+ readOnly: boolean
103
+ hint: string
104
+ maxsize: number
105
+ reference: string
106
+ reference_display: string
107
+ choiceOption: string
108
+ table: string
109
+ columnName: string
110
+ defaultValue: string
111
+ defaultDisplayValue?: string
112
+ use_dependent: boolean
113
+ fShowReferenceFinder: boolean
114
+ local: boolean
115
+ attributes: { [key: string]: unknown }
116
+ ref_qual: string
117
+ dependent_on: string
118
+ uiDisplayType?: string
119
+ }
120
+
121
+ /** Represents a child parameter within a complex object */
122
+ interface ChildParameter {
123
+ fieldFacetMap?: FieldFacetMap
124
+ value: string
125
+ displayValue?: string
126
+ scriptActive: boolean
127
+ script: { [key: string]: unknown }
128
+ children: ChildParameter[]
129
+ uiDisplayType: string
130
+ type_label: string
131
+ id: string
132
+ label: string
133
+ name: string
134
+ type: string
135
+ order: number
136
+ extended: boolean
137
+ mandatory: boolean
138
+ readOnly: boolean
139
+ hint: string
140
+ maxsize: number
141
+ reference: string
142
+ reference_display: string
143
+ choiceOption: string
144
+ table: string
145
+ columnName: string
146
+ defaultValue: string
147
+ defaultDisplayValue?: string
148
+ use_dependent: boolean
149
+ fShowReferenceFinder: boolean
150
+ local: boolean
151
+ attributes: { [key: string]: unknown }
152
+ ref_qual: string
153
+ dependent_on: string
154
+ parameter?: ParameterObject
155
+ }
156
+
157
+ /** Represents a variable entry in the values JSON */
158
+ interface VariableEntry {
159
+ name: string
160
+ value?: string
161
+ displayValue?: string
162
+ children?: ChildParameter[]
163
+ parameter?: ParameterObject | { [key: string]: unknown }
164
+ scriptActive?: boolean
165
+ script?: { [key: string]: unknown }
166
+ id?: string
167
+ }
168
+
169
+ /** Represents the structure of values JSON containing variables and inputs */
170
+ interface ValuesJSON {
171
+ variables?: VariableEntry[] | undefined
172
+ inputs?: VariableEntry[] | undefined
173
+ outputsToAssign?: VariableEntry[] | undefined
174
+ [key: string]: unknown
175
+ }
176
+ import { ApprovalDueDateShape, ApprovalRulesShape } from '../utils/flow-shapes'
177
+
178
+ // ============================================================================
179
+ // DATAPILL TRANSFORMATION ARCHITECTURE
180
+ // ============================================================================
181
+ // This section contains all datapill transformation logic for Flow Definitions.
182
+ // Datapills are placeholders in XML that reference other flow elements.
183
+ //
184
+ // TWO TYPES OF DATAPILLS:
185
+ //
186
+ // 1. **UUID-based pills** - References to action/subflow instances
187
+ // Format: {{uuid.property}}
188
+ // Example: {{f56a03c1-18f6-4f7b-9c61-0d4ff472b334.name}}
189
+ // Transforms to: actionInstance_1.name
190
+ // Uses: uuidToIdentifierMap built from instance variable names
191
+ //
192
+ // 2. **Semantic pills** - References to flow parameters
193
+ // Format: {{prefix.property}}
194
+ // Supported prefixes:
195
+ // - trigger: {{trigger.table_name}} → params.trigger.table_name
196
+ // - flow_variable: {{flow_variable.myVar}} → params.flowVariables.myVar
197
+ // - inputs: {{inputs.empName}} → params.inputs.empName (subflows only)
198
+ // - outputs: {{outputs.result}} → params.outputs.result (subflows only)
199
+ // - subflow: {{subflow.value}} → params.inputs.value (alias for inputs)
200
+ // Uses: convertPillStringToShape from pill-string-parser utility
201
+ //
202
+ // TRANSFORMATION STRATEGY:
203
+ // The transformDataPill function tries transformations in order:
204
+ // 1. Try UUID-based transformation (transformUuidBasedPill)
205
+ // 2. Try semantic transformation (convertPillStringToShape)
206
+ // 3. Return null if neither matches (keep original string value)
207
+ //
208
+ // This unified approach ensures all datapill types are handled consistently
209
+ // in a single location, making the code maintainable and extensible.
210
+ // ============================================================================
211
+
212
+ /**
213
+ * Traverse ancestors to find the topmost ArrowFunction and extract its parameter name
214
+ * This finds the ArrowFunction that's an argument to Flow/SubflowDefinition, ignoring nested
215
+ * ArrowFunctions from control flow APIs like If, ElseIf, ForEach.
216
+ *
217
+ * @param shape - The shape to start traversing from
218
+ * @returns The parameter name from the topmost ArrowFunction if found, otherwise undefined
219
+ */
220
+ function getArrowFunctionParamName(shape: Shape): string | undefined {
221
+ try {
222
+ let currentNode = shape.getOriginalNode()
223
+ let topmostArrowFunction: ReturnType<typeof currentNode.getParentIfKind> | undefined
224
+
225
+ // Traverse up the tree to find ALL ArrowFunctions and keep the topmost one
226
+ while (currentNode) {
227
+ const arrowFunc = currentNode.getParentIfKind(ts.SyntaxKind.ArrowFunction)
228
+ if (arrowFunc) {
229
+ // Found an ArrowFunction, keep track of it as potentially the topmost
230
+ topmostArrowFunction = arrowFunc
231
+ // Continue traversing to find if there's a parent ArrowFunction
232
+ currentNode = arrowFunc
233
+ } else {
234
+ // Try to go up one level
235
+ const parent = currentNode.getParent()
236
+ if (!parent || parent === currentNode) {
237
+ break
238
+ }
239
+ currentNode = parent
240
+ }
241
+ }
242
+
243
+ // Extract parameter name from the topmost ArrowFunction
244
+ if (topmostArrowFunction && 'getParameters' in topmostArrowFunction) {
245
+ const params = topmostArrowFunction.getParameters()
246
+ if (params && params.length > 0) {
247
+ const firstParam = params[0]
248
+ if (firstParam) {
249
+ const paramName = firstParam.getName()
250
+ if (paramName) {
251
+ return paramName
252
+ }
253
+ }
254
+ }
255
+ }
256
+ } catch (_e) {
257
+ // If any error occurs during traversal, just return undefined to fallback to default
258
+ }
259
+
260
+ return undefined
261
+ }
262
+
263
+ /**
264
+ * Recursively process a shape to find and transform datapills
265
+ * Handles nested ObjectShape and ArrayShape structures
266
+ *
267
+ * @param value - The shape to process
268
+ * @param uuidToIdentifierMap - Map of UUID to variable identifiers
269
+ * @param source - Source for shape creation
270
+ * @param diagnostics - Diagnostics for error reporting
271
+ * @param parameterName - Optional parameter name
272
+ * @param logger - Logger for error reporting
273
+ * @returns Object with transformed shape and propsChanged flag
274
+ */
275
+ function processShapeForDatapills(
276
+ value: Shape,
277
+ uuidToIdentifierMap: Map<string, IdentifierShape>,
278
+ source: Record,
279
+ diagnostics: Diagnostics,
280
+ logger: Logger,
281
+ parameterName?: string
282
+ ): { shape: Shape; propsChanged: boolean } {
283
+ // Check if value is already a datapill shape (PropertyAccessShape or TemplateExpressionShape)
284
+ if (value.if(PropertyAccessShape) || value.if(TemplateExpressionShape)) {
285
+ return {
286
+ shape: value,
287
+ propsChanged: false, // Already a datapill, no transformation needed
288
+ }
289
+ }
290
+ // Handle StringShape - may contain datapills
291
+ if (value.isString()) {
292
+ const stringValue = value.as(StringShape).getValue()
293
+ const transformed = transformDataPill(
294
+ stringValue,
295
+ uuidToIdentifierMap,
296
+ source,
297
+ diagnostics,
298
+ logger,
299
+ parameterName
300
+ )
301
+
302
+ if (transformed !== null) {
303
+ return {
304
+ shape: transformed,
305
+ propsChanged: true, // String was transformed into a datapill
306
+ }
307
+ }
308
+
309
+ return {
310
+ shape: value,
311
+ propsChanged: false,
312
+ }
313
+ }
314
+
315
+ // Handle ObjectShape - recursively process properties
316
+ if (
317
+ value.is(ObjectShape) ||
318
+ value.is(TemplateValueShape) ||
319
+ value.is(ApprovalRulesShape) ||
320
+ value.is(ApprovalDueDateShape)
321
+ ) {
322
+ const objEntries = value.isObject() ? value.entries() : value.getTemplateValue().entries()
323
+ const transformedObj: { [key: string]: Shape } = {}
324
+ let objPropsChanged = false
325
+
326
+ for (const entry of objEntries) {
327
+ const key: string = entry[0]
328
+ const propValue: Shape = entry[1]
329
+ const result = processShapeForDatapills(
330
+ propValue,
331
+ uuidToIdentifierMap,
332
+ source,
333
+ diagnostics,
334
+ logger,
335
+ parameterName
336
+ )
337
+ transformedObj[key] = result.shape
338
+
339
+ if (result.propsChanged) {
340
+ objPropsChanged = true
341
+ }
342
+ }
343
+
344
+ // If any property changed, rebuild the ObjectShape
345
+ if (objPropsChanged) {
346
+ let newObjectShape: ObjectShape | TemplateValueShape | ApprovalRulesShape | ApprovalDueDateShape
347
+ if (value.is(TemplateValueShape)) {
348
+ newObjectShape = new TemplateValueShape({
349
+ source: value.getSource(),
350
+ value: transformedObj,
351
+ })
352
+ } else if (value.is(ApprovalDueDateShape)) {
353
+ newObjectShape = new ApprovalDueDateShape({
354
+ source: value.getSource(),
355
+ value: transformedObj,
356
+ })
357
+ } else if (value.is(ApprovalRulesShape)) {
358
+ newObjectShape = new ApprovalRulesShape({
359
+ source: value.getSource(),
360
+ value: transformedObj,
361
+ })
362
+ } else {
363
+ newObjectShape = new ObjectShape({
364
+ source: value.getSource(),
365
+ properties: transformedObj,
366
+ })
367
+ }
368
+ return {
369
+ shape: newObjectShape,
370
+ propsChanged: true,
371
+ }
372
+ }
373
+
374
+ return {
375
+ shape: value,
376
+ propsChanged: false,
377
+ }
378
+ }
379
+
380
+ // Handle ArrayShape - recursively process elements
381
+ if (value instanceof ArrayShape) {
382
+ const elements = value.getElements()
383
+ const transformedElements: Shape[] = []
384
+ let arrayPropsChanged = false
385
+
386
+ for (const element of elements) {
387
+ const result = processShapeForDatapills(
388
+ element,
389
+ uuidToIdentifierMap,
390
+ source,
391
+ diagnostics,
392
+ logger,
393
+ parameterName
394
+ )
395
+ transformedElements.push(result.shape)
396
+
397
+ if (result.propsChanged) {
398
+ arrayPropsChanged = true
399
+ }
400
+ }
401
+
402
+ // If any element changed, rebuild the ArrayShape
403
+ if (arrayPropsChanged) {
404
+ const newArrayShape = new ArrayShape({
405
+ source: value.getSource(),
406
+ elements: transformedElements,
407
+ })
408
+ return {
409
+ shape: newArrayShape,
410
+ propsChanged: true,
411
+ }
412
+ }
413
+
414
+ return {
415
+ shape: value,
416
+ propsChanged: false,
417
+ }
418
+ }
419
+
420
+ // For any other shape type, pass through
421
+ return {
422
+ shape: value,
423
+ propsChanged: false,
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Transform data pill expressions in string values
429
+ * Handles two types of datapills:
430
+ * 1. UUID-based pills: {{uuid.property}} → references to action/subflow instances (e.g., actionInstance_1.name)
431
+ * 2. Semantic pills: {{trigger.property}}, {{flow_variable.name}} → references to params (e.g., params.trigger.name)
432
+ *
433
+ * @param stringValue - String that may contain datapill patterns
434
+ * @param uuidToIdentifierMap - Map of UUID to variable identifiers (for action/subflow references)
435
+ * @param source - Source for shape creation
436
+ * @param diagnostics - Diagnostics for error reporting
437
+ * @param parameterName - Optional parameter name to use instead of "params" (e.g., from ArrowFunction)
438
+ * @returns Transformed shape or null if no transformation needed
439
+ */
440
+ /**
441
+ * Wrap a PropertyAccessShape or IdentifierShape with wfa.dataPill(expression, 'type') call
442
+ * This creates a CallExpressionShape that wraps the expression
443
+ *
444
+ * @param expression - The PropertyAccessShape or IdentifierShape to wrap
445
+ * @param source - Source for shape creation
446
+ * @param dataType - The data type string (default: 'string')
447
+ * @returns CallExpressionShape wrapping the expression with wfa.dataPill()
448
+ */
449
+ function wrapWithDataPillCall(
450
+ expression: PropertyAccessShape | IdentifierShape,
451
+ source: Record,
452
+ dataType = 'string'
453
+ ): CallExpressionShape {
454
+ return new CallExpressionShape({
455
+ source,
456
+ callee: 'wfa.dataPill',
457
+ args: [expression, new StringLiteralShape({ source, literalText: dataType })],
458
+ })
459
+ }
460
+
461
+ /**
462
+ * Extract all datapill names from a string value containing datapill patterns
463
+ * Handles both UUID-based pills ({{uuid.property}}) and semantic pills ({{prefix.path}})
464
+ *
465
+ * @param stringValue - String that may contain datapill patterns
466
+ * @returns Array of datapill names (e.g., ["trigger.table_name", "uuid.record.number"])
467
+ */
468
+ function extractDataPillNames(stringValue: string): string[] {
469
+ const dataPillNames: string[] = []
470
+
471
+ // Match all datapill patterns: {{...}}
472
+ const pillPattern = /\{\{([^}]+)\}\}/g
473
+ const matches = Array.from(stringValue.matchAll(pillPattern))
474
+
475
+ for (const match of matches) {
476
+ if (match[1]) {
477
+ // Extract the content inside {{}} and split by | to get just the name part
478
+ // (some pills may have type info like {{pill.name|string}})
479
+ const pillContent = match[1].split('|')[0]
480
+ if (pillContent) {
481
+ dataPillNames.push(pillContent)
482
+ }
483
+ }
484
+ }
485
+
486
+ return dataPillNames
487
+ }
488
+
489
+ /**
490
+ * Wrap all PropertyAccessShape expressions in a TemplateExpressionShape with wfa.dataPill()
491
+ * Creates a new TemplateExpressionShape with wrapped expressions in each span
492
+ *
493
+ * @param templateExpr - The TemplateExpressionShape, PropertyAccessShape, or IdentifierShape to process
494
+ * @param source - Source for shape creation
495
+ * @param labelCacheMap - Optional map of datapill names to their types
496
+ * @param originalString - Optional original string value to extract datapill name from
497
+ * @returns New TemplateExpressionShape with wrapped expressions
498
+ */
499
+ function wrapWithWfaDataPillExpression(
500
+ templateExpr: TemplateExpressionShape | PropertyAccessShape | IdentifierShape,
501
+ source: Record,
502
+ labelCacheMap?: Map<string, string>,
503
+ originalString?: string
504
+ ): TemplateExpressionShape | CallExpressionShape {
505
+ // Extract all datapill names from the original string
506
+ const dataPillNames = originalString ? extractDataPillNames(originalString) : []
507
+
508
+ if (templateExpr.is(PropertyAccessShape)) {
509
+ let dataType = 'string'
510
+ if (labelCacheMap && dataPillNames.length > 0) {
511
+ // For a single PropertyAccessShape, use the first (and likely only) datapill name
512
+ const firstPillName = dataPillNames[0]
513
+ if (firstPillName) {
514
+ // labelCacheMap returns the type string directly, not an object
515
+ dataType = labelCacheMap.get(firstPillName) || 'string'
516
+ }
517
+ }
518
+ return wrapWithDataPillCall(templateExpr, source, dataType)
519
+ }
520
+
521
+ if (templateExpr.is(IdentifierShape)) {
522
+ return wrapWithDataPillCall(templateExpr, source)
523
+ }
524
+
525
+ const originalSpans = templateExpr.getSpans()
526
+ const wrappedSpans: TemplateSpanShape[] = []
527
+ let pillIndex = 0
528
+
529
+ for (const span of originalSpans) {
530
+ const expression = span.getExpression()
531
+ const literalText = span.getLiteralText()
532
+
533
+ // Wrap the expression with wfa.dataPill() if it's a PropertyAccessShape or IdentifierShape
534
+ if (expression instanceof PropertyAccessShape || expression instanceof IdentifierShape) {
535
+ let dataType = 'string'
536
+ if (labelCacheMap && pillIndex < dataPillNames.length) {
537
+ // Use the corresponding datapill name for this expression
538
+ const pillName = dataPillNames[pillIndex]
539
+ if (pillName) {
540
+ // labelCacheMap returns the type string directly, not an object
541
+ dataType = labelCacheMap.get(pillName) || 'string'
542
+ }
543
+ pillIndex++
544
+ }
545
+ const wrappedExpr = wrapWithDataPillCall(expression, source, dataType)
546
+
547
+ wrappedSpans.push(
548
+ new TemplateSpanShape({
549
+ source,
550
+ expression: wrappedExpr,
551
+ literalText,
552
+ })
553
+ )
554
+ } else {
555
+ // Keep as-is if not a PropertyAccessShape or IdentifierShape
556
+ wrappedSpans.push(span)
557
+ }
558
+ }
559
+
560
+ return new TemplateExpressionShape({
561
+ source,
562
+ literalText: templateExpr.getLiteralText(),
563
+ spans: wrappedSpans,
564
+ })
565
+ }
566
+
567
+ /**
568
+ * Information about a single datapill found in a string
569
+ */
570
+ interface PillInfo {
571
+ matchStart: number
572
+ matchEnd: number
573
+ matchText: string // Original {{...}} pattern
574
+ expression: PropertyAccessShape | IdentifierShape
575
+ type: 'uuid' | 'semantic' // For debugging/tracking
576
+ }
577
+
578
+ /**
579
+ * Extract all datapills (both UUID-based and semantic) from a string.
580
+ * Uses a single regex to find all {{...}} patterns, then determines type using conditional logic.
581
+ *
582
+ * @param stringValue - String that may contain datapill patterns
583
+ * @param uuidToIdentifierMap - Map of UUID to variable identifiers (includes forEach parameter names)
584
+ * @param source - Source for shape creation
585
+ * @param diagnostics - Diagnostics for error reporting
586
+ * @param parameterName - Parameter name for semantic pills (default 'params')
587
+ * @returns Array of pills with their positions and transformed expressions
588
+ */
589
+ function extractAllPills(
590
+ stringValue: string,
591
+ uuidToIdentifierMap: Map<string, IdentifierShape>,
592
+ source: Record,
593
+ _diagnostics: Diagnostics,
594
+ parameterName?: string
595
+ ): PillInfo[] {
596
+ const pills: PillInfo[] = []
597
+
598
+ // SINGLE regex to find ALL pill patterns: {{anything}}
599
+ const pillRegex = /\{\{([^}]+)\}\}/g
600
+
601
+ // Loop through ALL matches
602
+ for (const match of stringValue.matchAll(pillRegex)) {
603
+ const fullMatch = match[0] // e.g., "{{uuid.field}}" or "{{trigger.name}}"
604
+ const content = match[1] // e.g., "uuid.field" or "trigger.name"
605
+
606
+ if (!content) {
607
+ continue
608
+ }
609
+
610
+ const matchStart = match.index ?? 0
611
+ const matchEnd = matchStart + fullMatch.length
612
+
613
+ // Try UUID pattern first (more specific)
614
+ // UUID format: 8-4-4-4-12 hex digits followed by dot and property path
615
+ const uuidMatch = content.match(
616
+ /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.([a-zA-Z_][a-zA-Z0-9_.]*[a-zA-Z0-9_])$/
617
+ )
618
+
619
+ if (uuidMatch) {
620
+ // This is a UUID-based pill
621
+ const uuid = uuidMatch[1]
622
+ const property = uuidMatch[2]
623
+
624
+ if (!uuid || !property) {
625
+ continue
626
+ }
627
+
628
+ const identifier = uuidToIdentifierMap.get(uuid)
629
+
630
+ if (identifier) {
631
+ // PRESERVE EXISTING LOGIC: Apply forEach parameter handling
632
+ const isForEachParam = isForEachParameter(identifier, uuid)
633
+ let expression: PropertyAccessShape | IdentifierShape
634
+
635
+ if (isForEachParam) {
636
+ // Special case for forEach: if property is exactly 'item', return just the identifier
637
+ if (property === 'item') {
638
+ expression = identifier
639
+ } else if (property.startsWith('item.')) {
640
+ // Strip 'item.' prefix for forEach parameters
641
+ const propertyPath = property.substring(5)
642
+ expression = new PropertyAccessShape({
643
+ source,
644
+ elements: [identifier, propertyPath],
645
+ })
646
+ } else {
647
+ // Property doesn't start with 'item.' - preserve as is
648
+ expression = new PropertyAccessShape({
649
+ source,
650
+ elements: [identifier, property],
651
+ })
652
+ }
653
+ } else {
654
+ // NOT a forEach parameter - preserve full property path
655
+ expression = new PropertyAccessShape({
656
+ source,
657
+ elements: [identifier, property],
658
+ })
659
+ }
660
+
661
+ pills.push({
662
+ matchStart,
663
+ matchEnd,
664
+ matchText: fullMatch,
665
+ expression,
666
+ type: 'uuid',
667
+ })
668
+ }
669
+ // If UUID not in map, skip this pill (existing behavior)
670
+ continue
671
+ }
672
+
673
+ // Not a UUID pill - try semantic transformation
674
+ const parsed = parseSinglePill(fullMatch, true)
675
+
676
+ if (!parsed) {
677
+ // Invalid pill format - skip
678
+ continue
679
+ }
680
+
681
+ if (isUuidPrefix(parsed.prefix)) {
682
+ // This is a UUID prefix but wasn't caught above - skip
683
+ continue
684
+ }
685
+
686
+ const tsRoot = mapPillPrefixToTsRoot(parsed.prefix, parameterName)
687
+
688
+ if (!tsRoot) {
689
+ // Unknown prefix - skip
690
+ continue
691
+ }
692
+
693
+ // Handle schema references (no property access) like {{flowVariables}} or {{outputs}}
694
+ let expression: PropertyAccessShape | null
695
+ if (parsed.path === '') {
696
+ // Schema reference - tsRoot already contains full path
697
+ expression = createPropertyAccessFromPath(tsRoot, source)
698
+ } else {
699
+ // Property access like {{trigger.table_name}}
700
+ const fullPath = `${tsRoot}.${parsed.path}`
701
+ expression = createPropertyAccessFromPath(fullPath, source)
702
+ }
703
+
704
+ if (expression) {
705
+ pills.push({
706
+ matchStart,
707
+ matchEnd,
708
+ matchText: fullMatch,
709
+ expression,
710
+ type: 'semantic',
711
+ })
712
+ }
713
+ }
714
+
715
+ // Pills are already in order since we process matches sequentially
716
+ // No need to sort or remove duplicates - each match is processed once
717
+ return pills
718
+ }
719
+
720
+ /**
721
+ * Build a TemplateExpressionShape from a list of pills with their positions.
722
+ * Preserves all text (leading, in-between, trailing) from the original string.
723
+ *
724
+ * @param stringValue - Original string containing pills and text
725
+ * @param pills - List of pills with positions (in order)
726
+ * @param source - Source for shape creation
727
+ * @returns TemplateExpressionShape with all pills and text
728
+ */
729
+ function buildTemplateExpression(stringValue: string, pills: PillInfo[], source: Record): TemplateExpressionShape {
730
+ const spans: TemplateSpanShape[] = []
731
+
732
+ // Head: text before first pill (leading text)
733
+ const firstPill = pills[0]
734
+ if (!firstPill) {
735
+ // Should never happen, but TypeScript wants this check
736
+ throw new Error('buildTemplateExpression called with empty pills array')
737
+ }
738
+ const literalText = stringValue.substring(0, firstPill.matchStart)
739
+
740
+ // Create spans for each pill (handles 1, 2, 20, or any number of pills)
741
+ for (let i = 0; i < pills.length; i++) {
742
+ const pill = pills[i]
743
+ if (!pill) {
744
+ continue
745
+ }
746
+
747
+ const nextPill = pills[i + 1]
748
+
749
+ // Text after this pill (in-between text or trailing text)
750
+ // - If there's a next pill: text between current and next pill
751
+ // - If this is the last pill: text after this pill to end of string (trailing)
752
+ const afterText = stringValue.substring(pill.matchEnd, nextPill ? nextPill.matchStart : stringValue.length)
753
+
754
+ spans.push(
755
+ new TemplateSpanShape({
756
+ source,
757
+ expression: pill.expression,
758
+ literalText: afterText,
759
+ })
760
+ )
761
+ }
762
+
763
+ return new TemplateExpressionShape({
764
+ source,
765
+ literalText, // head (leading text)
766
+ spans, // pills with in-between and trailing text
767
+ })
768
+ }
769
+
770
+ function transformDataPill(
771
+ stringValue: string,
772
+ uuidToIdentifierMap: Map<string, IdentifierShape>,
773
+ source: Record,
774
+ diagnostics: Diagnostics,
775
+ logger: Logger,
776
+ parameterName?: string
777
+ ): CallExpressionShape | TemplateExpressionShape | PropertyAccessShape | null {
778
+ // Special case: Schema references for SetFlowVariables/AssignSubflowOutputs
779
+ // {{flowVariables}} and {{outputs}} should remain as bare PropertyAccessShape without wrapping
780
+ if (stringValue === '{{flowVariables}}' || stringValue === '{{outputs}}') {
781
+ const allPills = extractAllPills(stringValue, uuidToIdentifierMap, source, diagnostics, parameterName)
782
+ if (allPills.length === 1 && allPills[0]?.expression.is(PropertyAccessShape)) {
783
+ return allPills[0].expression as PropertyAccessShape
784
+ }
785
+ }
786
+
787
+ // Extract label_cache from source Record and create type map
788
+ let labelCacheMap: Map<string, string> | undefined
789
+ try {
790
+ const labelCacheValue = source.get('label_cache')?.getValue()
791
+ if (labelCacheValue && typeof labelCacheValue === 'string') {
792
+ labelCacheMap = createLableCacheNameToTypeMap(labelCacheValue, logger)
793
+ }
794
+ } catch (_error) {
795
+ // If label_cache extraction fails, continue with default 'string' type
796
+ labelCacheMap = undefined
797
+ }
798
+
799
+ // Extract all pills (both UUID-based and semantic) from the string
800
+ const allPills = extractAllPills(stringValue, uuidToIdentifierMap, source, diagnostics, parameterName)
801
+
802
+ if (allPills.length === 0) {
803
+ return null // No pills found
804
+ }
805
+
806
+ // Check if single standalone pill (optimization)
807
+ const firstPill = allPills[0]
808
+ if (allPills.length === 1 && firstPill && firstPill.matchStart === 0 && firstPill.matchEnd === stringValue.length) {
809
+ // Single pill covering entire string - return PropertyAccessShape/IdentifierShape directly
810
+ return wrapWithWfaDataPillExpression(firstPill.expression, source, labelCacheMap, stringValue)
811
+ }
812
+
813
+ // Build template expression from all pills
814
+ const templateExpr = buildTemplateExpression(stringValue, allPills, source)
815
+
816
+ return wrapWithWfaDataPillExpression(templateExpr, source, labelCacheMap, stringValue)
817
+ }
818
+
819
+ /**
820
+ * Helper function to check if a UUID corresponds to a forEach flow logic instance
821
+ * @param uuid - The UUID to check
822
+ * @returns true if the UUID maps to a forEach logic instance, false otherwise
823
+ */
824
+ function isForEachByUuid(uuid: string): boolean {
825
+ const record = uuidToRecordMap.get(uuid)
826
+ if (record && record.getTable() === 'sys_hub_flow_logic_instance_v2') {
827
+ const logicDefId = record.get('logic_definition')?.ifString()?.getValue()
828
+ if (logicDefId) {
829
+ const logicName = FlowLogicSysId.getLogicName(logicDefId)
830
+ return logicName === FLOW_LOGIC.FOR_EACH
831
+ }
832
+ }
833
+ return false
834
+ }
835
+
836
+ /**
837
+ * Checks if an identifier is actually a forEach parameter by inspecting its AST context.
838
+ * This prevents incorrectly stripping 'item' from property paths when 'item' is an actual field name.
839
+ *
840
+ * @param identifier - The IdentifierShape to check
841
+ * @param uuid - Optional UUID of the flow logic instance (used during XML→Fluent)
842
+ * @returns true if the identifier is a forEach parameter, false otherwise
843
+ */
844
+ function isForEachParameter(identifier: IdentifierShape, uuid?: string): boolean {
845
+ const originalSource = identifier.getOriginalSource()
846
+ const identifierName = identifier.getName()
847
+
848
+ // Branch 1: XML → Fluent (originalSource is a Record)
849
+ if (!ts.Node.isNode(originalSource)) {
850
+ // Check if Record is a forEach logic definition
851
+ if (originalSource instanceof Record && originalSource.getTable() === 'sys_hub_flow_logic_v2') {
852
+ const logicDefId = originalSource.get('logic_definition')?.ifString()?.getValue()
853
+ if (logicDefId) {
854
+ const logicName = FlowLogicSysId.getLogicName(logicDefId)
855
+ return logicName === FLOW_LOGIC.FOR_EACH
856
+ }
857
+ }
858
+ // Fallback: check name pattern for backwards compatibility
859
+ return /^item(_\d+)?$/.test(identifierName)
860
+ }
861
+
862
+ // Branch 2: Fluent → XML (originalSource is a ts.Node)
863
+ // Try AST-based detection first
864
+ const arrowFn = (originalSource as ts.Node).getFirstAncestorByKind(ts.SyntaxKind.ArrowFunction)
865
+ if (arrowFn) {
866
+ const callExpr = arrowFn.getParentIfKind(ts.SyntaxKind.CallExpression)
867
+ if (callExpr) {
868
+ const callExprName = getCallExpressionName(callExpr)
869
+ const isThirdArgument = callExpr.getArguments()[2] === arrowFn
870
+ if (callExprName === FLOW_LOGIC.FOR_EACH && isThirdArgument) {
871
+ return true // AST check succeeded
872
+ }
873
+ }
874
+ }
875
+
876
+ // AST check failed or arrow function not found - use UUID fallback
877
+ return uuid ? isForEachByUuid(uuid) : false
878
+ }
879
+
880
+ /**
881
+ * Clean up config object by removing uuid (if it matches sys_id) and empty string fields
882
+ *
883
+ * @param configShape - The config ObjectShape to clean up
884
+ * @param instanceShape - The instance shape (used to get the source record)
885
+ * @returns Cleaned up config shape or original if no cleanup needed
886
+ */
887
+ function cleanupConfigShape(configShape: Shape, instanceShape: Shape): Shape {
888
+ if (!(configShape instanceof ObjectShape)) {
889
+ return configShape
890
+ }
891
+
892
+ const configObj = configShape
893
+ const uuid = configObj.get('uuid')?.ifString()?.getValue()
894
+ const sysIdShape = configObj.get('$id')
895
+
896
+ if (!uuid || !sysIdShape) {
897
+ return configShape
898
+ }
899
+
900
+ // Get the sys_id value (could be a NowIdShape or string)
901
+ let sysId: string | undefined
902
+ const instanceSource = instanceShape.getSource()
903
+ if (sysIdShape.if(NowIdShape)) {
904
+ sysId = instanceSource instanceof Record ? String(instanceSource.getId().getValue()) : undefined
905
+ } else if (sysIdShape.isString()) {
906
+ sysId = sysIdShape.asString().getValue()
907
+ }
908
+
909
+ // Only clean up if uuid matches the sys_id (after converting uuid to sys_id format)
910
+ if (!sysId || uuidToSysId(uuid) !== sysId) {
911
+ return configShape
912
+ }
913
+
914
+ // Create new config without uuid property and empty string values
915
+ const configEntries = configObj.entries()
916
+ const newConfigProps: { [key: string]: Shape } = {}
917
+
918
+ for (const [key, value] of configEntries) {
919
+ // Skip uuid field
920
+ if (key === 'uuid') {
921
+ continue
922
+ }
923
+ // Skip fields with empty string values
924
+ if (value.isString() && value.asString().getValue() === '') {
925
+ continue
926
+ }
927
+ newConfigProps[key] = value
928
+ }
929
+
930
+ return new ObjectShape({
931
+ source: configObj.getSource(),
932
+ properties: newConfigProps,
933
+ })
934
+ }
935
+
936
+ const documentationRelationship: Relationship = {
937
+ descendant: true,
938
+ via: { name: 'name', element: 'element' },
939
+ }
940
+
941
+ const complexObjectRelationship: Relationship = {
942
+ descendant: true,
943
+ via: [complexObjectMatchesIoRecord],
944
+ }
945
+
946
+ const inputOutputRelationships: Relationship = {
947
+ via: 'model',
948
+ descendant: true,
949
+ relationships: {
950
+ sys_documentation: documentationRelationship,
951
+ sys_choice: {
952
+ via: { name: 'name', element: 'element' },
953
+ descendant: true,
954
+ },
955
+ sys_complex_object: complexObjectRelationship,
956
+ },
957
+ }
958
+
959
+ const variableRelationship: Relationship = {
960
+ via: 'model',
961
+ descendant: true,
962
+ relationships: {
963
+ sys_documentation: documentationRelationship,
964
+ sys_complex_object: complexObjectRelationship,
965
+ },
966
+ }
967
+
968
+ const legacyRelationship: Relationship = {
969
+ via: 'flow',
970
+ descendant: true,
971
+ relationships: {
972
+ sys_variable_value: {
973
+ via: 'document_key',
974
+ descendant: true,
975
+ },
976
+ sys_element_mapping: {
977
+ via: 'id',
978
+ descendant: true,
979
+ },
980
+ sys_hub_input_scripts: {
981
+ via: 'instance',
982
+ descendant: true,
983
+ },
984
+ },
985
+ }
986
+
987
+ const commonFlowRelationships: Relationships = {
988
+ sys_hub_flow_input: inputOutputRelationships,
989
+ sys_hub_flow_output: inputOutputRelationships,
990
+ sys_hub_flow_variable: variableRelationship,
991
+ sys_hub_flow_stage: {
992
+ via: 'flow',
993
+ descendant: true,
994
+ },
995
+ sys_hub_action_instance: legacyRelationship,
996
+ sys_hub_sub_flow_instance: {
997
+ ...legacyRelationship,
998
+ relationships: {
999
+ ...legacyRelationship.relationships,
1000
+ sys_hub_sub_flow_instance_inputs: {
1001
+ via: 'model',
1002
+ descendant: true,
1003
+ },
1004
+ },
1005
+ },
1006
+ sys_hub_trigger_instance: legacyRelationship,
1007
+ sys_hub_flow_logic: {
1008
+ via: 'flow',
1009
+ descendant: true,
1010
+ relationships: {
1011
+ sys_element_mapping: {
1012
+ via: 'id',
1013
+ descendant: true,
1014
+ },
1015
+ sys_hub_flow_logic_ext_input: {
1016
+ via: 'model',
1017
+ descendant: true,
1018
+ },
1019
+ sys_hub_input_scripts: {
1020
+ via: 'instance',
1021
+ descendant: true,
1022
+ },
1023
+ },
1024
+ },
1025
+ sys_hub_pill_compound: {
1026
+ via: 'attached_to',
1027
+ descendant: true,
1028
+ },
1029
+ sys_hub_alias_mapping: {
1030
+ via: 'source_id',
1031
+ descendant: true,
1032
+ },
1033
+ sys_flow_cat_variable_model: {
1034
+ via: 'id',
1035
+ descendant: true,
1036
+ relationships: {
1037
+ sys_flow_cat_variable: {
1038
+ via: 'flow_catalog_model',
1039
+ descendant: true,
1040
+ },
1041
+ },
1042
+ },
1043
+ }
1044
+
1045
+ const commonInstanceV2Relationships: Relationships = {
1046
+ sys_hub_trigger_instance_v2: {
1047
+ via: 'flow',
1048
+ descendant: true,
1049
+ },
1050
+ sys_hub_action_instance_v2: {
1051
+ via: 'flow',
1052
+ descendant: true,
1053
+ },
1054
+ sys_hub_flow_logic_instance_v2: {
1055
+ via: 'flow',
1056
+ descendant: true,
1057
+ },
1058
+ sys_hub_sub_flow_instance_v2: {
1059
+ via: 'flow',
1060
+ descendant: true,
1061
+ },
1062
+ }
1063
+
1064
+ /**
1065
+ * Checks if children array contains datapill references (e.g., "{{trigger.field}}")
1066
+ */
1067
+ function childrenContainDatapills(children: ChildParameter[]): boolean {
1068
+ if (!Array.isArray(children)) {
1069
+ return false
1070
+ }
1071
+
1072
+ for (const child of children) {
1073
+ // Check if value contains datapill markers
1074
+ if (isDataPill(child.value)) {
1075
+ return true
1076
+ }
1077
+ // Recursively check nested children
1078
+ if (child.children && Array.isArray(child.children) && childrenContainDatapills(child.children)) {
1079
+ return true
1080
+ }
1081
+ }
1082
+
1083
+ return false
1084
+ }
1085
+
1086
+ /**
1087
+ * Builds the parameter object for a variable with schema information
1088
+ */
1089
+ function buildParameterObject(
1090
+ varName: string,
1091
+ _children: ChildParameter[],
1092
+ serviceNowValue: string,
1093
+ logger: Logger
1094
+ ): ParameterObject | { [key: string]: unknown } {
1095
+ // Parse the ServiceNow formatted value to extract schema
1096
+ try {
1097
+ const parsed = JSON.parse(serviceNowValue)
1098
+ if (!parsed.complexObjectSchema) {
1099
+ return {}
1100
+ }
1101
+
1102
+ // Extract the schema key (e.g., "FlowDesigner:FDxxx")
1103
+ const schemaKeys = Object.keys(parsed.complexObjectSchema)
1104
+ const mainSchemaKey = schemaKeys.find((key) => key.startsWith('FlowDesigner:') && !key.includes('.$'))
1105
+
1106
+ if (!mainSchemaKey) {
1107
+ return {}
1108
+ }
1109
+
1110
+ const schema = parsed.complexObjectSchema[mainSchemaKey]
1111
+ const typeFacetsKey = `${mainSchemaKey}.$type_facets`
1112
+ const typeFacets = parsed.complexObjectSchema[typeFacetsKey]?.SimpleMapFacet
1113
+
1114
+ if (!typeFacets) {
1115
+ return {}
1116
+ }
1117
+
1118
+ const typeFacetsObj = JSON.parse(typeFacets)
1119
+
1120
+ // Build parameter object
1121
+ const parameter: ParameterObject = {
1122
+ children: [],
1123
+ type_label: typeFacetsObj.uiTypeLabel || 'Object',
1124
+ id: typeFacetsObj.id || '',
1125
+ label: typeFacetsObj.label || varName,
1126
+ name: varName,
1127
+ type: typeFacetsObj.uiType || 'object',
1128
+ order: parseInt(typeFacetsObj.order || '0', 10),
1129
+ extended: false,
1130
+ mandatory: typeFacetsObj.mandatory === 'true',
1131
+ readOnly: typeFacetsObj.read_only === 'true',
1132
+ hint: typeFacetsObj.hint || '',
1133
+ maxsize: parseInt(typeFacetsObj.max_length || '0', 10),
1134
+ reference: '',
1135
+ reference_display: '',
1136
+ choiceOption: typeFacetsObj.choiceOption || '',
1137
+ table: '',
1138
+ columnName: '',
1139
+ defaultValue: typeFacetsObj.default_value || '',
1140
+ use_dependent: false,
1141
+ fShowReferenceFinder: false,
1142
+ local: false,
1143
+ attributes: {},
1144
+ ref_qual: '',
1145
+ dependent_on: '',
1146
+ }
1147
+
1148
+ // Copy attributes
1149
+ for (const [key, value] of Object.entries(typeFacetsObj)) {
1150
+ if (
1151
+ ![
1152
+ 'label',
1153
+ 'type',
1154
+ 'order',
1155
+ 'mandatory',
1156
+ 'read_only',
1157
+ 'hint',
1158
+ 'max_length',
1159
+ 'choiceOption',
1160
+ 'default_value',
1161
+ ].includes(key)
1162
+ ) {
1163
+ parameter.attributes[key] = value
1164
+ }
1165
+ }
1166
+
1167
+ // Build children array from schema
1168
+ for (const [fieldName, _fieldType] of Object.entries(schema)) {
1169
+ if (fieldName.includes('.$') || fieldName === 'name$' || fieldName === COMPLEX_OBJECT_COLLECTION_FIELD) {
1170
+ continue
1171
+ }
1172
+
1173
+ const fieldFacetsKey = `${fieldName}.$field_facets`
1174
+ const fieldFacets = schema[fieldFacetsKey]?.SimpleMapFacet
1175
+
1176
+ if (!fieldFacets) {
1177
+ continue
1178
+ }
1179
+
1180
+ const fieldFacetsObj = JSON.parse(fieldFacets)
1181
+
1182
+ // Build fieldFacetMap with mapped datapills
1183
+ const fieldFacetMap: FieldFacetMap = {
1184
+ choiceOption: fieldFacetsObj.choiceOption || '',
1185
+ uiTypeLabel: fieldFacetsObj.uiTypeLabel || '',
1186
+ }
1187
+
1188
+ // Check if this field has a datapill mapping
1189
+ if (fieldFacetsObj.mapped) {
1190
+ fieldFacetMap.mapped = fieldFacetsObj.mapped
1191
+ }
1192
+
1193
+ const childParam: ChildParameter = {
1194
+ fieldFacetMap,
1195
+ value: fieldFacetsObj.default_value || '',
1196
+ scriptActive: false,
1197
+ script: {},
1198
+ children: [],
1199
+ uiDisplayType: fieldFacetsObj.uiType || '',
1200
+ type_label: fieldFacetsObj.uiTypeLabel || '',
1201
+ id: '',
1202
+ label: fieldFacetsObj.label || fieldName,
1203
+ name: fieldName,
1204
+ type: fieldFacetsObj.uiType || 'string',
1205
+ order: parseInt(fieldFacetsObj.order || '0', 10),
1206
+ extended: false,
1207
+ mandatory: fieldFacetsObj.mandatory === 'true',
1208
+ readOnly: fieldFacetsObj.read_only === 'true',
1209
+ hint: fieldFacetsObj.hint || '',
1210
+ maxsize: parseInt(fieldFacetsObj.max_length || '0', 10),
1211
+ reference: '',
1212
+ reference_display: '',
1213
+ choiceOption: fieldFacetsObj.choiceOption || '',
1214
+ table: '',
1215
+ columnName: '',
1216
+ defaultValue: fieldFacetsObj.default_value || '',
1217
+ defaultDisplayValue: '',
1218
+ use_dependent: false,
1219
+ fShowReferenceFinder: false,
1220
+ local: false,
1221
+ attributes: {},
1222
+ ref_qual: '',
1223
+ dependent_on: '',
1224
+ }
1225
+
1226
+ parameter.children.push(childParam)
1227
+ }
1228
+
1229
+ return parameter
1230
+ } catch (e) {
1231
+ logger.error(`Error building parameter object for variable ${varName}: ${e}`)
1232
+ return {}
1233
+ }
1234
+ }
1235
+
1236
+ /**
1237
+ * Enriches children array with parameter metadata from ServiceNow formatted value
1238
+ */
1239
+ function enrichChildrenWithParameters(
1240
+ children: ChildParameter[],
1241
+ serviceNowValue: string,
1242
+ logger: Logger,
1243
+ clearValues = false
1244
+ ): ChildParameter[] {
1245
+ if (!children || !Array.isArray(children)) {
1246
+ return children
1247
+ }
1248
+
1249
+ try {
1250
+ const parsed = JSON.parse(serviceNowValue)
1251
+ if (!parsed.complexObjectSchema) {
1252
+ return children
1253
+ }
1254
+
1255
+ // Extract the schema
1256
+ const schemaKeys = Object.keys(parsed.complexObjectSchema)
1257
+ const mainSchemaKey = schemaKeys.find((key) => key.startsWith('FlowDesigner:') && !key.includes('.$'))
1258
+
1259
+ if (!mainSchemaKey) {
1260
+ return children
1261
+ }
1262
+
1263
+ const schema = parsed.complexObjectSchema[mainSchemaKey]
1264
+
1265
+ return children.map((child) => {
1266
+ const fieldFacetsKey = `${child.name}.$field_facets`
1267
+ const fieldFacets = schema[fieldFacetsKey]?.SimpleMapFacet
1268
+
1269
+ if (!fieldFacets) {
1270
+ return child
1271
+ }
1272
+
1273
+ const fieldFacetsObj = JSON.parse(fieldFacets)
1274
+
1275
+ const parameter: ParameterObject = {
1276
+ children: [],
1277
+ uiDisplayType: fieldFacetsObj.uiType || '',
1278
+ type_label: fieldFacetsObj.uiTypeLabel || '',
1279
+ id: '',
1280
+ label: fieldFacetsObj.label || child.name,
1281
+ name: child.name,
1282
+ type: fieldFacetsObj.uiType || 'string',
1283
+ order: parseInt(fieldFacetsObj.order || '0', 10),
1284
+ extended: false,
1285
+ mandatory: fieldFacetsObj.mandatory === 'true',
1286
+ readOnly: fieldFacetsObj.read_only === 'true',
1287
+ hint: fieldFacetsObj.hint || '',
1288
+ maxsize: parseInt(fieldFacetsObj.max_length || '0', 10),
1289
+ reference: '',
1290
+ reference_display: '',
1291
+ choiceOption: fieldFacetsObj.choiceOption || '',
1292
+ table: '',
1293
+ columnName: '',
1294
+ defaultValue: fieldFacetsObj.default_value || '',
1295
+ defaultDisplayValue: '',
1296
+ use_dependent: false,
1297
+ fShowReferenceFinder: false,
1298
+ local: false,
1299
+ attributes: {},
1300
+ ref_qual: '',
1301
+ dependent_on: '',
1302
+ }
1303
+
1304
+ return {
1305
+ ...child,
1306
+ id: '',
1307
+ value: clearValues ? '' : child.value,
1308
+ displayValue: clearValues ? '' : child.displayValue || '',
1309
+ parameter,
1310
+ scriptActive: false,
1311
+ script: {},
1312
+ }
1313
+ })
1314
+ } catch (e) {
1315
+ logger.error('Error enriching children with parameters:', e)
1316
+ return children
1317
+ }
1318
+ }
1319
+
1320
+ /**
1321
+ * Post-processes setFlowVariables values to apply ServiceNow complex object serialization.
1322
+ * For variables with FlowObject/FlowArray types, replaces plain JSON with the proper
1323
+ * ServiceNow format including $cv wrappers, schema, version, and serializationFormat.
1324
+ *
1325
+ * @param valuesJSON - The values array from the flow logic instance
1326
+ * @param flowDefinitionRecord - The flow definition record containing variable definitions
1327
+ * @returns Updated values array with complex objects properly serialized
1328
+ */
1329
+ function postProcessSetFlowVariables(valuesJSON: ValuesJSON, flowDefinitionRecord: Record, logger: Logger): ValuesJSON {
1330
+ if (!valuesJSON) {
1331
+ return valuesJSON
1332
+ }
1333
+
1334
+ // Helper function to process a single variable entry
1335
+ const processVariable = (variable: VariableEntry): VariableEntry => {
1336
+ // Check if this variable has children array
1337
+ // Note: Empty arrays should still be processed to generate ServiceNow complex object format
1338
+ if (!variable.children || !Array.isArray(variable.children)) {
1339
+ return variable
1340
+ }
1341
+
1342
+ // For variables with empty children, check if the value is an empty array that needs processing
1343
+ const hasEmptyArrayValue = variable.children.length === 0 && variable.value === '[]'
1344
+ if (variable.children.length === 0 && !hasEmptyArrayValue) {
1345
+ return variable
1346
+ }
1347
+
1348
+ // Check if children contain datapills
1349
+ const hasDatapills = childrenContainDatapills(variable.children)
1350
+
1351
+ // If value is empty but has datapills, we need to get the ServiceNow format from variables array
1352
+ if (!variable.value && hasDatapills) {
1353
+ // Find the corresponding entry in the variables array
1354
+ const correspondingVar = valuesJSON.variables?.find((v: VariableEntry) => v.name === variable.name)
1355
+ if (correspondingVar?.value) {
1356
+ // Build parameter using the variables array value (which has ServiceNow format)
1357
+ const parameter = buildParameterObject(variable.name, variable.children, correspondingVar.value, logger)
1358
+ // Enrich children with parameter objects
1359
+ const enrichedChildren = enrichChildrenWithParameters(variable.children, correspondingVar.value, logger)
1360
+ return {
1361
+ ...variable,
1362
+ children: enrichedChildren,
1363
+ parameter,
1364
+ }
1365
+ }
1366
+ return variable
1367
+ }
1368
+
1369
+ // If no value and no datapills, skip processing
1370
+ if (!variable.value) {
1371
+ return variable
1372
+ }
1373
+
1374
+ // Try to parse the value to see if it's plain JSON (not already in ServiceNow format)
1375
+ let parsedValue: unknown
1376
+ try {
1377
+ parsedValue = JSON.parse(variable.value)
1378
+ } catch (_e) {
1379
+ // If it's not valid JSON, leave it as is
1380
+ logger.error('Error parsing value:', variable.value)
1381
+ return variable
1382
+ }
1383
+
1384
+ // Check if parsedValue is null or not an object
1385
+ if (!parsedValue || typeof parsedValue !== 'object') {
1386
+ return variable
1387
+ }
1388
+
1389
+ // Check if it's already in ServiceNow format (has version, complexObjectSchema, etc.)
1390
+ if ('version' in parsedValue || 'complexObjectSchema' in parsedValue || 'serializationFormat' in parsedValue) {
1391
+ // Already in ServiceNow format, check if we need to add parameter
1392
+ if (hasDatapills) {
1393
+ const parameter = buildParameterObject(variable.name, variable.children, variable.value, logger)
1394
+ // Enrich children with parameter objects
1395
+ const enrichedChildren = enrichChildrenWithParameters(variable.children, variable.value, logger)
1396
+ return {
1397
+ ...variable,
1398
+ children: enrichedChildren,
1399
+ parameter,
1400
+ }
1401
+ }
1402
+ return variable
1403
+ }
1404
+
1405
+ // Check if it's a plain object or array (potential FlowObject/FlowArray)
1406
+ const isObject = parsedValue && typeof parsedValue === 'object' && !Array.isArray(parsedValue)
1407
+ const isArray = Array.isArray(parsedValue)
1408
+
1409
+ if (!isObject && !isArray) {
1410
+ return variable
1411
+ }
1412
+
1413
+ // Use resolveComplexInput to get the proper ServiceNow format
1414
+ const resolved = resolveComplexInput(
1415
+ variable.name,
1416
+ parsedValue,
1417
+ flowDefinitionRecord,
1418
+ 'sys_hub_flow_variable'
1419
+ ) as { value: unknown; internalType: string | undefined; children?: unknown[] }
1420
+
1421
+ if (resolved?.value && typeof resolved.value === 'string' && resolved.value !== variable.value) {
1422
+ // Use children from resolveComplexInput if available (has correct child_name), otherwise use existing
1423
+ const childrenToUse = (resolved.children as ChildParameter[]) || variable.children
1424
+
1425
+ // Build parameter object if children contain datapills
1426
+ const parameter = hasDatapills
1427
+ ? buildParameterObject(variable.name, childrenToUse, resolved.value, logger)
1428
+ : {}
1429
+
1430
+ // Enrich children with parameter objects
1431
+ const enrichedChildren = enrichChildrenWithParameters(childrenToUse, resolved.value, logger)
1432
+
1433
+ return {
1434
+ ...variable,
1435
+ value: resolved.value,
1436
+ displayValue: resolved.value,
1437
+ children: enrichedChildren,
1438
+ parameter,
1439
+ }
1440
+ }
1441
+
1442
+ return variable
1443
+ }
1444
+
1445
+ // Process variables array first
1446
+ const updatedVariables =
1447
+ valuesJSON.variables && Array.isArray(valuesJSON.variables)
1448
+ ? valuesJSON.variables.map((v: VariableEntry) => processVariable(v))
1449
+ : valuesJSON.variables
1450
+
1451
+ // Create intermediate object with updated variables for inputs processing
1452
+ const intermediateJSON = {
1453
+ ...valuesJSON,
1454
+ variables: updatedVariables,
1455
+ }
1456
+
1457
+ // Then process inputs array, which can now reference the updated variables
1458
+ const updatedInputs =
1459
+ intermediateJSON.inputs && Array.isArray(intermediateJSON.inputs)
1460
+ ? intermediateJSON.inputs.map((v: VariableEntry) => {
1461
+ // Update the closure to use intermediateJSON instead of valuesJSON
1462
+ const hasDatapills = v.children && Array.isArray(v.children) && childrenContainDatapills(v.children)
1463
+ if (!v.value && hasDatapills && v.children) {
1464
+ const correspondingVar = intermediateJSON.variables?.find(
1465
+ (variable: VariableEntry) => variable.name === v.name
1466
+ )
1467
+ if (correspondingVar?.value) {
1468
+ const parameter = buildParameterObject(v.name, v.children, correspondingVar.value, logger)
1469
+ // Enrich children with parameter objects, clearing values for inputs array
1470
+ const enrichedChildren = enrichChildrenWithParameters(
1471
+ v.children,
1472
+ correspondingVar.value,
1473
+ logger,
1474
+ true
1475
+ )
1476
+ return { ...v, children: enrichedChildren, parameter }
1477
+ }
1478
+ }
1479
+ return processVariable(v)
1480
+ })
1481
+ : intermediateJSON.inputs
1482
+
1483
+ return {
1484
+ ...intermediateJSON,
1485
+ inputs: updatedInputs,
1486
+ }
1487
+ }
1488
+
1489
+ /**
1490
+ * Post-processes assignSubflowOutputs to apply complex object serialization for FlowObject and FlowArray outputs.
1491
+ * This is necessary because assignSubflowOutputs generates plain JSON for FlowObject/FlowArray values,
1492
+ * but ServiceNow requires them to be in the complex object format with schemas.
1493
+ *
1494
+ * Similar to postProcessSetFlowVariables, but processes outputsToAssign array instead of variables array.
1495
+ *
1496
+ * @param valuesJSON - The values JSON containing outputsToAssign array
1497
+ * @param subflowDefinitionRecord - The subflow definition record for schema lookup
1498
+ * @returns Updated valuesJSON with properly formatted complex objects
1499
+ */
1500
+ function postProcessAssignSubflowOutputs(
1501
+ valuesJSON: ValuesJSON,
1502
+ subflowDefinitionRecord: Record,
1503
+ logger: Logger
1504
+ ): ValuesJSON {
1505
+ if (!valuesJSON || !valuesJSON.outputsToAssign || !Array.isArray(valuesJSON.outputsToAssign)) {
1506
+ return valuesJSON
1507
+ }
1508
+
1509
+ // Helper function to process a single output entry
1510
+ const processOutput = (output: VariableEntry): VariableEntry => {
1511
+ // Check if this output has children array
1512
+ // Note: Empty arrays should still be processed to generate ServiceNow complex object format
1513
+ if (!output.children || !Array.isArray(output.children)) {
1514
+ return output
1515
+ }
1516
+
1517
+ // For outputs with empty children, check if the value is an empty array that needs processing
1518
+ const hasEmptyArrayValue = output.children.length === 0 && output.value === '[]'
1519
+ if (output.children.length === 0 && !hasEmptyArrayValue) {
1520
+ return output
1521
+ }
1522
+
1523
+ // Check if children contain datapills
1524
+ const hasDatapills = childrenContainDatapills(output.children)
1525
+
1526
+ // If no value and no datapills, skip processing
1527
+ if (!output.value) {
1528
+ return output
1529
+ }
1530
+
1531
+ // Try to parse the value to see if it's plain JSON (not already in ServiceNow format)
1532
+ let parsedValue: unknown
1533
+ try {
1534
+ parsedValue = JSON.parse(output.value)
1535
+ } catch (_e) {
1536
+ // If it's not valid JSON, leave it as is
1537
+ logger.error('Error parsing value:', output.value)
1538
+ return output
1539
+ }
1540
+
1541
+ // Check if parsedValue is null or not an object
1542
+ if (!parsedValue || typeof parsedValue !== 'object') {
1543
+ return output
1544
+ }
1545
+
1546
+ // Check if it's already in ServiceNow format (has version, complexObjectSchema, etc.)
1547
+ if ('version' in parsedValue || 'complexObjectSchema' in parsedValue || 'serializationFormat' in parsedValue) {
1548
+ // Already in ServiceNow format, check if we need to add parameter
1549
+ if (hasDatapills) {
1550
+ const parameter = buildParameterObject(output.name, output.children, output.value, logger)
1551
+ // Enrich children with parameter objects
1552
+ const enrichedChildren = enrichChildrenWithParameters(output.children, output.value, logger)
1553
+ return {
1554
+ ...output,
1555
+ children: enrichedChildren,
1556
+ parameter,
1557
+ }
1558
+ }
1559
+ return output
1560
+ }
1561
+
1562
+ // Check if it's a plain object or array (potential FlowObject/FlowArray)
1563
+ const isObject = parsedValue && typeof parsedValue === 'object' && !Array.isArray(parsedValue)
1564
+ const isArray = Array.isArray(parsedValue)
1565
+
1566
+ if (!isObject && !isArray) {
1567
+ // Not a complex type, leave as is
1568
+ return output
1569
+ }
1570
+
1571
+ // Use resolveComplexInput to get the proper ServiceNow format
1572
+ // For subflow outputs, we look in sys_hub_flow_output table (same as flow outputs)
1573
+ const resolved = resolveComplexInput(
1574
+ output.name,
1575
+ parsedValue,
1576
+ subflowDefinitionRecord,
1577
+ 'sys_hub_flow_output'
1578
+ ) as { value: unknown; internalType: string | undefined; children?: unknown[] }
1579
+
1580
+ if (resolved?.value && typeof resolved.value === 'string' && resolved.value !== output.value) {
1581
+ // Use children from resolveComplexInput if available (has correct child_name), otherwise use existing
1582
+ const childrenToUse = (resolved.children as ChildParameter[]) || output.children
1583
+
1584
+ // Build parameter object if children contain datapills
1585
+ const parameter = hasDatapills
1586
+ ? buildParameterObject(output.name, childrenToUse, resolved.value, logger)
1587
+ : {}
1588
+
1589
+ // Enrich children with parameter objects
1590
+ let enrichedChildren = enrichChildrenWithParameters(childrenToUse, resolved.value, logger)
1591
+
1592
+ // For assignSubflowOutputs, prepend parent output name to child names (dotted format)
1593
+ // e.g., "tag" becomes "tags.tag"
1594
+ enrichedChildren = enrichedChildren.map((child) => ({
1595
+ ...child,
1596
+ name: `${output.name}.${child.name}`,
1597
+ }))
1598
+
1599
+ return {
1600
+ ...output,
1601
+ value: resolved.value,
1602
+ displayValue: resolved.value,
1603
+ children: enrichedChildren,
1604
+ parameter,
1605
+ }
1606
+ }
1607
+
1608
+ return output
1609
+ }
1610
+
1611
+ // Process outputsToAssign array
1612
+ const updatedOutputs = valuesJSON.outputsToAssign.map((output: VariableEntry) => processOutput(output))
1613
+
1614
+ return {
1615
+ ...valuesJSON,
1616
+ outputsToAssign: updatedOutputs,
1617
+ }
1618
+ }
1619
+
1620
+ export const FlowDefinitionPlugin = Plugin.create({
1621
+ name: 'FlowDefinitionPlugin',
1622
+ records: {
1623
+ sys_hub_flow: {
1624
+ relationships: {
1625
+ ...commonFlowRelationships,
1626
+ ...commonInstanceV2Relationships,
1627
+ sys_flow_record_trigger: {
1628
+ via: 'remote_trigger_id',
1629
+ inverse: true,
1630
+ descendant: true,
1631
+ },
1632
+ sys_trigger_runner_mapping: {
1633
+ via: 'identifier',
1634
+ descendant: true,
1635
+ },
1636
+ sys_flow_trigger_plan: {
1637
+ via: 'plan_id',
1638
+ descendant: true,
1639
+ },
1640
+ sys_flow_timer_trigger: {
1641
+ via: 'remote_trigger_id',
1642
+ inverse: true,
1643
+ descendant: true,
1644
+ relationships: {
1645
+ sys_flow_trigger_auto_script: {
1646
+ via: 'trigger',
1647
+ descendant: true,
1648
+ },
1649
+ },
1650
+ },
1651
+ sys_variable_value: {
1652
+ via: 'document_key',
1653
+ descendant: true,
1654
+ },
1655
+ sys_translated_text: {
1656
+ via: 'documentkey',
1657
+ descendant: true,
1658
+ },
1659
+ sys_hub_flow_snapshot: {
1660
+ via: 'parent_flow',
1661
+ descendant: true,
1662
+ relationships: {
1663
+ ...commonFlowRelationships,
1664
+ ...commonInstanceV2Relationships,
1665
+ },
1666
+ },
1667
+ sys_flow_subflow_plan: {
1668
+ via: 'plan_id',
1669
+ descendant: true,
1670
+ },
1671
+ sys_hub_flow_variable: {
1672
+ via: 'model',
1673
+ descendant: true,
1674
+ },
1675
+ sys_hub_flow_stage: {
1676
+ via: 'flow',
1677
+ descendant: true,
1678
+ },
1679
+ },
1680
+ getUpdateName(record) {
1681
+ return { success: true, value: `${record.getTable()}_${record.getId().getValue()}` }
1682
+ },
1683
+ toFile(record, { config, database }) {
1684
+ return generateXML(record, { config, database })
1685
+ },
1686
+ async diff(existing, incoming, descendants, context) {
1687
+ return deleteMultipleDiff(existing, incoming, descendants, context)
1688
+ },
1689
+ async toShape(record, { descendants, diagnostics, transform, database, logger }) {
1690
+ let type = record.get('type')?.getValue()
1691
+ if (!type) {
1692
+ logger.warn(`sys_hub_flow record has no type specified, defaulting to 'flow'`)
1693
+ type = 'flow'
1694
+ } else if (type !== 'flow' && type !== 'subflow') {
1695
+ logger.error(
1696
+ `Skipping sys_hub_flow record: expected type 'flow' or 'subflow', but received '${type}'`
1697
+ )
1698
+ return { success: false }
1699
+ }
1700
+ const isSubflow = type === 'subflow'
1701
+ const isFlow = type === 'flow'
1702
+
1703
+ const inputs = buildVariableShapes(descendants.query('sys_hub_flow_input'), descendants, logger)
1704
+ const outputs = buildVariableShapes(descendants.query('sys_hub_flow_output'), descendants, logger)
1705
+
1706
+ //If Flow has unsupported datatypes for inputs/outputs, fallback to Record() API
1707
+ if (inputs === undefined || outputs === undefined) {
1708
+ return { success: false }
1709
+ }
1710
+
1711
+ const flowVariables = buildVariableShapes(descendants.query('sys_hub_flow_variable'), descendants)
1712
+
1713
+ //Check if flow has unsupported descendants are present
1714
+ if (checkForUnsupportedFlowDescendants(descendants, record, logger)) {
1715
+ return { success: false }
1716
+ }
1717
+
1718
+ const trigger = descendants.query('sys_hub_trigger_instance_v2')[0] // There will be only 1 trigger per flow
1719
+
1720
+ let triggerShape: CallExpressionShape | UndefinedShape = new UndefinedShape({
1721
+ source: record.getSource(),
1722
+ })
1723
+
1724
+ if (isFlow) {
1725
+ if (trigger) {
1726
+ const triggerShapeResult = await transform.recordToShape(
1727
+ trigger,
1728
+ database,
1729
+ TriggerInstancePlugin
1730
+ )
1731
+ if (triggerShapeResult.success) {
1732
+ triggerShape = triggerShapeResult.value.as(CallExpressionShape)
1733
+ } else {
1734
+ return { success: false }
1735
+ }
1736
+ }
1737
+ }
1738
+
1739
+ const actionInstances = descendants.query('sys_hub_action_instance_v2', {
1740
+ flow: record.getId().getValue(), // This is to skip snapshot records
1741
+ })
1742
+ const flowLogicInstance = descendants.query('sys_hub_flow_logic_instance_v2', {
1743
+ flow: record.getId().getValue(),
1744
+ })
1745
+ const subflowInstance = descendants.query('sys_hub_sub_flow_instance_v2', {
1746
+ flow: record.getId().getValue(),
1747
+ })
1748
+
1749
+ const order = (rec: Record) => Number(rec.get('order')?.getValue())
1750
+
1751
+ // Build UUID map from ALL instances (actions, subflows, and flow logic)
1752
+ const allInstances = [...actionInstances, ...subflowInstance, ...flowLogicInstance]
1753
+ const recordToShapeMap = new Map<Record, Shape>()
1754
+ const allInstanceShapes: Shape[] = []
1755
+
1756
+ for (const instance of allInstances) {
1757
+ const instanceShapeResult = await transform.recordToShape(
1758
+ instance,
1759
+ database,
1760
+ FlowInstancePlugin,
1761
+ FlowLogicPlugin
1762
+ )
1763
+ if (instanceShapeResult.success) {
1764
+ const shape = instanceShapeResult.value
1765
+ recordToShapeMap.set(instance, shape)
1766
+ allInstanceShapes.push(shape)
1767
+ }
1768
+ }
1769
+
1770
+ // Build UUID map from all transformed shapes and their records
1771
+ // Use recordToShapeMap to access ui_id from records (datapills reference ui_id, not $id)
1772
+ const uuidToIdentifierMap = buildUuidToIdentifierMap(recordToShapeMap)
1773
+
1774
+ // For the flow body, only use top-level instances (those without parent_ui_id)
1775
+ const topLevelInstances = allInstances
1776
+ .filter(
1777
+ (instance) =>
1778
+ !instance.get('parent_ui_id').isDefined() || instance.get('parent_ui_id').getValue() === ''
1779
+ )
1780
+ .sort((a, b) => order(a) - order(b))
1781
+
1782
+ const instanceShapes: Shape[] = []
1783
+ for (const instance of topLevelInstances) {
1784
+ const shape = recordToShapeMap.get(instance)
1785
+ if (!shape) {
1786
+ logger.error(`Failed to find shape for instance ${instance.getId().getValue()}`)
1787
+ return { success: false }
1788
+ }
1789
+ instanceShapes.push(shape)
1790
+ }
1791
+
1792
+ // Transform datapill expressions in instance props
1793
+ // This section handles TWO types of datapills:
1794
+ // 1. UUID-based pills: {{uuid.property}} → references to previous action/subflow instances (e.g., {{f56a03c1-18f6-4f7b-9c61-0d4ff472b334.name}} → actionInstance_1.name)
1795
+ // 2. Semantic pills: {{trigger.property}}, {{flow_variable.name}} → references to flow parameters (e.g., {{trigger.table_name}} → params.trigger.table_name)
1796
+
1797
+ // Extract parameter name from the topmost ArrowFunction
1798
+ // This allows us to use the actual parameter name instead of hardcoding "params"
1799
+ let paramName = DEFAULT_PARAM_NAME // Default fallback
1800
+ const firstInstance = instanceShapes[0]
1801
+ if (firstInstance) {
1802
+ paramName = getArrowFunctionParamName(firstInstance) || DEFAULT_PARAM_NAME
1803
+ }
1804
+
1805
+ // Process all instances (Action, Subflow, and Flow Logic) recursively
1806
+ // This handles datapill transformation for all instance types and their nested children
1807
+ instanceShapes.forEach((instanceShape: Shape, index) => {
1808
+ const result = processInstanceForDatapills(
1809
+ instanceShape,
1810
+ uuidToIdentifierMap,
1811
+ record,
1812
+ diagnostics,
1813
+ paramName,
1814
+ logger,
1815
+ processShapeForDatapills,
1816
+ cleanupConfigShape
1817
+ )
1818
+
1819
+ // Only update if transformations occurred
1820
+ if (result.propsChanged) {
1821
+ instanceShapes[index] = result.shape
1822
+ }
1823
+ })
1824
+ const flowBody = new ArrowFunctionShape({
1825
+ source: record,
1826
+ parameters: [new IdentifierShape({ source: record, name: paramName })],
1827
+ statements: instanceShapes,
1828
+ })
1829
+
1830
+ // Create the CallExpressionShape for both flow and subflow
1831
+ const callExpression = new CallExpressionShape({
1832
+ source: record,
1833
+ callee: isSubflow ? SUBFLOW_API_NAME : FLOW_API_NAME,
1834
+ args: [
1835
+ record.transform(({ $ }) => ({
1836
+ $id: $.val(NowIdShape.from(record)),
1837
+ name: $,
1838
+ description: $.def(EMPTY_STRING),
1839
+ protection: $.from('sys_policy').def(EMPTY_STRING),
1840
+ runAs: $.from('run_as').def(RUN_AS_DEFAULT),
1841
+ runWithRoles: $.from('run_with_roles')
1842
+ .map((r) => {
1843
+ const roles = r.ifString()?.getValue() ?? ''
1844
+ return roles
1845
+ .split(',')
1846
+ .map((role) => role.trim())
1847
+ .filter((role) => role)
1848
+ })
1849
+ .def([]),
1850
+ flowPriority: $.from('flow_priority')
1851
+ .map((p) => p.ifString()?.getValue() || FLOW_PRIORITY_DEFAULT)
1852
+ .def(FLOW_PRIORITY_DEFAULT),
1853
+ ...(isSubflow
1854
+ ? {
1855
+ annotation: $.def(EMPTY_STRING),
1856
+ category: $.def(EMPTY_STRING),
1857
+ access: $.def(ACCESS_DEFAULT),
1858
+ inputs: $.val(inputs).def({}),
1859
+ outputs: $.val(outputs).def({}),
1860
+ }
1861
+ : {}),
1862
+ flowVariables: $.val(flowVariables).def({}),
1863
+ })),
1864
+ ...(isFlow ? [triggerShape, flowBody] : [flowBody]),
1865
+ ],
1866
+ })
1867
+
1868
+ // For subflows, wrap in VariableStatementShape with isExported true
1869
+ // For flows, return the CallExpressionShape directly
1870
+ if (isSubflow) {
1871
+ // Try to get the existing identifier from the record source, fallback to slugified name
1872
+ const flowName =
1873
+ getIdentifierFromRecord(record) ??
1874
+ slugifyString(String(record.get('internal_name')?.getValue() || record.get('name')?.getValue()))
1875
+ return {
1876
+ success: true,
1877
+ value: new VariableStatementShape({
1878
+ source: record,
1879
+ variableName: flowName,
1880
+ initializer: callExpression,
1881
+ isExported: true,
1882
+ }),
1883
+ }
1884
+ } else {
1885
+ return {
1886
+ success: true,
1887
+ value: callExpression,
1888
+ }
1889
+ }
1890
+ },
1891
+ },
1892
+ sys_hub_flow_input: {
1893
+ coalesce: ['model', 'element'],
1894
+ },
1895
+ sys_hub_flow_output: {
1896
+ coalesce: ['model', 'element'],
1897
+ },
1898
+ sys_complex_object: {
1899
+ coalesce: ['name'],
1900
+ },
1901
+ sys_hub_flow_variable: {
1902
+ coalesce: ['model', 'element'],
1903
+ },
1904
+ sys_hub_flow_stage: {
1905
+ coalesce: ['flow', 'value'],
1906
+ },
1907
+ sys_variable_value: {
1908
+ coalesce: ['document_key', 'variable'],
1909
+ },
1910
+ },
1911
+ shapes: [
1912
+ {
1913
+ shape: CallExpressionShape,
1914
+ fileTypes: ['fluent'],
1915
+ async toRecord(callExpression, { factory, diagnostics, transform, logger }) {
1916
+ const isFlow = callExpression.getCallee() === FLOW_API_NAME
1917
+ const isSubflow = callExpression.getCallee() === SUBFLOW_API_NAME
1918
+
1919
+ if (!isFlow && !isSubflow) {
1920
+ return { success: false }
1921
+ }
1922
+
1923
+ let flowConfiguration: ObjectShape,
1924
+ triggerInstance: Record | undefined,
1925
+ flowBody: ArrowFunctionShape | undefined
1926
+ const relatedRecords: Record[] = []
1927
+
1928
+ if (isFlow) {
1929
+ flowConfiguration = callExpression.getArgument(0).asObject()
1930
+
1931
+ const triggerInstanceCE = callExpression.getArgument(1)?.if(UndefinedShape)
1932
+ ? undefined
1933
+ : callExpression.getArgument(1)?.as(CallExpressionShape)
1934
+ const triggerInstanceRecord = triggerInstanceCE
1935
+ ? await transform.toRecord(triggerInstanceCE, TriggerInstancePlugin)
1936
+ : undefined
1937
+ triggerInstance = triggerInstanceRecord?.success ? triggerInstanceRecord.value : undefined
1938
+
1939
+ flowBody = !callExpression.getArgument(2)?.if(UndefinedShape)
1940
+ ? callExpression.getArgument(2)?.as(ArrowFunctionShape)
1941
+ : undefined
1942
+ } else {
1943
+ flowConfiguration = callExpression.getArgument(0).asObject()
1944
+ flowBody = !callExpression.getArgument(1)?.if(UndefinedShape)
1945
+ ? callExpression.getArgument(1)?.as(ArrowFunctionShape)
1946
+ : undefined
1947
+ }
1948
+
1949
+ const roles = flowConfiguration.get('runWithRoles').ifArray()?.getElements() ?? []
1950
+ const flowDefinitionRecord = await factory.createRecord({
1951
+ source: callExpression,
1952
+ table: 'sys_hub_flow',
1953
+ explicitId: flowConfiguration.get('$id'),
1954
+ properties: flowConfiguration.transform(({ $ }) => ({
1955
+ name: $,
1956
+ internal_name: $.from('name').map((n) => slugifyString(n.getValue())),
1957
+ annotation: $.def(EMPTY_STRING),
1958
+ description: $.def(EMPTY_STRING),
1959
+ access: $.def(ACCESS_DEFAULT),
1960
+ category: $.def(EMPTY_STRING),
1961
+ sys_policy: $.from('protection').def(EMPTY_STRING),
1962
+ run_as: $.from('runAs').def(RUN_AS_DEFAULT),
1963
+ generation_source: $.def(GENERATION_SOURCE),
1964
+ show_draft_actions: $.def(BOOLEAN_FALSE_STRING),
1965
+ flow_priority: $.from('flowPriority').def(FLOW_PRIORITY_DEFAULT),
1966
+ run_with_roles: $.val(
1967
+ Array.from(
1968
+ new Set(roles.map((r) => r.ifRecord()?.getId().getValue() ?? r.getValue()))
1969
+ ).join(',')
1970
+ ),
1971
+ active: $.val(false),
1972
+ status: $.val(FLOW_STATUS_DRAFT),
1973
+ type: isFlow ? $.def(FLOW_TYPE_FLOW) : $.def(FLOW_TYPE_SUBFLOW),
1974
+ version: $.def(FLOW_VERSION),
1975
+ label_cache: $.val('[]'),
1976
+ })),
1977
+ })
1978
+ if (isFlow && triggerInstance) {
1979
+ // Push updated trigger instance record
1980
+ relatedRecords.push(
1981
+ triggerInstance.merge({
1982
+ flow: flowDefinitionRecord.getId().getValue(),
1983
+ })
1984
+ )
1985
+ }
1986
+
1987
+ // ------------------------------------------------------------
1988
+ // Handle FlowVariables() argument (flow-level variables)
1989
+ // Each variable becomes a separate sys_hub_flow_variable record
1990
+ // ------------------------------------------------------------
1991
+ if (flowConfiguration.has('flowVariables')) {
1992
+ const variablesConfig = flowConfiguration.get('flowVariables').asObject()
1993
+
1994
+ // --------------------------------------------------------------------
1995
+ // Validate each variable is created using a supported FlowValueType
1996
+ // --------------------------------------------------------------------
1997
+ variablesConfig.entries().forEach(([key, value]) => {
1998
+ if (!value.if(CallExpressionShape)) {
1999
+ diagnostics.error(
2000
+ value.getOriginalNode(),
2001
+ `flowVariables.${key} is not a valid variable expression.`
2002
+ )
2003
+ return
2004
+ }
2005
+ // Use the shared validation function
2006
+ validateFlowVariableCall(value.as(CallExpressionShape), `flowVariables.${key}`, diagnostics)
2007
+ })
2008
+
2009
+ const flowVarRecords = await processFlowVariables(
2010
+ variablesConfig,
2011
+ flowDefinitionRecord,
2012
+ factory,
2013
+ diagnostics
2014
+ )
2015
+ relatedRecords.push(...flowVarRecords)
2016
+ }
2017
+
2018
+ if (isSubflow) {
2019
+ // Process subflow inputs and outputs
2020
+ const inputsConfig = flowConfiguration.get('inputs').ifObject()?.asObject()
2021
+ const inputRecords = await processFlowInputs(
2022
+ inputsConfig,
2023
+ flowDefinitionRecord,
2024
+ factory,
2025
+ diagnostics
2026
+ )
2027
+ relatedRecords.push(...inputRecords)
2028
+
2029
+ const outputsConfig = flowConfiguration.get('outputs').ifObject()?.asObject()
2030
+ const outputRecords = await processFlowOutputs(
2031
+ outputsConfig,
2032
+ flowDefinitionRecord,
2033
+ factory,
2034
+ diagnostics
2035
+ )
2036
+ relatedRecords.push(...outputRecords)
2037
+ }
2038
+
2039
+ const allInstances = flowBody?.getStatements().filter((stmt) => !(stmt instanceof UndefinedShape))
2040
+
2041
+ // Create lookup maps for flow variables and subflow inputs
2042
+ const flowVarDefMap = createDefinitionMap(relatedRecords, 'sys_hub_flow_variable')
2043
+ const subflowInputDefMap = createDefinitionMap(relatedRecords, 'sys_hub_flow_input')
2044
+
2045
+ // Extract trigger data to be used during label cache generation
2046
+ const triggerInstanceShape = isFlow
2047
+ ? callExpression.getArgument(1)?.if(UndefinedShape)
2048
+ ? undefined
2049
+ : callExpression.getArgument(1)?.as(CallExpressionShape)
2050
+ : undefined
2051
+
2052
+ const triggerData = triggerInstanceShape
2053
+ ? {
2054
+ inputs: extractTriggerInputs(triggerInstanceShape),
2055
+ outputs: extractTriggerOutputs(triggerInstanceShape),
2056
+ }
2057
+ : undefined
2058
+ // Map to aggregate data pill metadata for label_cache
2059
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic label cache entries
2060
+ let labelCacheMap = new Map<string, any>()
2061
+ // Map to track forEach instances with their metadata for label generation
2062
+ let forEachMetadataMap = new Map<string, { stepNumber: number; tableName: string | null }>()
2063
+
2064
+ let order = 1
2065
+ for (const [, v] of (allInstances ?? []).entries()) {
2066
+ // Validate trigger placement: triggers must be the second argument to Flow(), not in the body
2067
+ const callExpr = v instanceof VariableStatementShape ? v.getInitializer() : v
2068
+ if (callExpr instanceof CallExpressionShape && callExpr.getCallee() === TRIGGER_INSTANCE_API_NAME) {
2069
+ diagnostics.error(
2070
+ callExpr.getOriginalNode(),
2071
+ 'Trigger instances must be passed as the second argument to Flow(), not inside the flow body'
2072
+ )
2073
+ continue
2074
+ }
2075
+ const instanceRecord = await getRecordFromFlowInstaceShape(v, transform)
2076
+
2077
+ if (instanceRecord) {
2078
+ instanceRecord.flat().forEach((rec) => {
2079
+ // For subflow instances, values will be in subflow_inputs, for other instances (action/flow logic) it will be in values
2080
+ const inputsKey =
2081
+ rec.getTable() === 'sys_hub_sub_flow_instance_v2' ? 'subflow_inputs' : 'values'
2082
+ let valuesJSON: ValuesJSON =
2083
+ (rec.get(inputsKey)?.getValue() as ValuesJSON) || ({} as ValuesJSON)
2084
+
2085
+ // Post-process setFlowVariables to apply complex object serialization
2086
+ if (rec.getTable() === 'sys_hub_flow_logic_instance_v2') {
2087
+ const valuesObj = valuesJSON
2088
+
2089
+ // Process setFlowVariables
2090
+ if (valuesObj.variables && valuesObj.variables.length > 0) {
2091
+ // Create a record-like object that includes all related records for resolveComplexInput
2092
+ // resolveComplexInput calls .flat() on the parentDef to get all records
2093
+ const combinedRecord = {
2094
+ ...flowDefinitionRecord,
2095
+ flat: () => [...flowDefinitionRecord.flat(), ...relatedRecords],
2096
+ } as Record
2097
+ valuesJSON = postProcessSetFlowVariables(valuesJSON, combinedRecord, logger)
2098
+ }
2099
+
2100
+ // Process assignSubflowOutputs
2101
+ if (valuesObj.outputsToAssign && valuesObj.outputsToAssign.length > 0) {
2102
+ // Create a record-like object that includes all related records for resolveComplexInput
2103
+ // resolveComplexInput calls .flat() on the parentDef to get all records
2104
+ const combinedRecord = {
2105
+ ...flowDefinitionRecord,
2106
+ flat: () => [...flowDefinitionRecord.flat(), ...relatedRecords],
2107
+ } as Record
2108
+ valuesJSON = postProcessAssignSubflowOutputs(valuesJSON, combinedRecord, logger)
2109
+ }
2110
+ }
2111
+
2112
+ const recUiId = rec.get('ui_id').asString()?.getValue()
2113
+ // Extract data pills from values array and track their usage
2114
+ labelCacheMap = extractDataPillsFromValuesArray(valuesJSON, recUiId, labelCacheMap)
2115
+ let finalOrder = order.toString()
2116
+
2117
+ const previousOrder = order - 1
2118
+ if (previousOrder > 0) {
2119
+ const previousRecord = relatedRecords.find(
2120
+ (r) => r.get('order')?.getValue() === previousOrder.toString()
2121
+ )
2122
+
2123
+ if (
2124
+ previousRecord &&
2125
+ previousRecord.get('logic_definition')?.getValue() === DO_IN_PARALLEL_BLOCK_SYS_ID
2126
+ ) {
2127
+ finalOrder = `${previousOrder}${PARALLEL_ORDER_SEPARATOR}${order}`
2128
+ }
2129
+ }
2130
+ // Update forEach metadata for label cache generation
2131
+ forEachMetadataMap = updateForEachMetadata(rec, order, flowVarDefMap, forEachMetadataMap)
2132
+ labelCacheMap = extractLabelCache(
2133
+ flowVarDefMap,
2134
+ subflowInputDefMap,
2135
+ relatedRecords,
2136
+ labelCacheMap,
2137
+ triggerData,
2138
+ forEachMetadataMap
2139
+ )
2140
+
2141
+ relatedRecords.push(
2142
+ rec.merge({
2143
+ flow: flowDefinitionRecord.getId().getValue(),
2144
+ order: finalOrder,
2145
+ [inputsKey]: gzipSync(JSON.stringify(valuesJSON)).toString('base64'),
2146
+ })
2147
+ )
2148
+ order++
2149
+ })
2150
+ } else {
2151
+ diagnostics.error(
2152
+ v.getOriginalNode(),
2153
+ `Unknown instance type in Flow ${flowDefinitionRecord.getId().getValue()}`
2154
+ )
2155
+ }
2156
+ }
2157
+
2158
+ const labelCacheData = Array.from(labelCacheMap.values())
2159
+ const flowDefWithMetadata =
2160
+ labelCacheData.length > 0
2161
+ ? flowDefinitionRecord.merge({
2162
+ label_cache: JSON.stringify(labelCacheData),
2163
+ })
2164
+ : flowDefinitionRecord
2165
+
2166
+ return {
2167
+ success: true,
2168
+ value: flowDefWithMetadata.with(...relatedRecords),
2169
+ }
2170
+ },
2171
+ },
2172
+ ],
2173
+ })