@servicenow/sdk-build-plugins 4.6.1 → 4.7.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 (279) hide show
  1. package/dist/acl-plugin.js +0 -3
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.js +0 -2
  4. package/dist/applicability-plugin.js.map +1 -1
  5. package/dist/application-menu-plugin.js +0 -2
  6. package/dist/application-menu-plugin.js.map +1 -1
  7. package/dist/arrow-function-plugin.js +0 -1
  8. package/dist/arrow-function-plugin.js.map +1 -1
  9. package/dist/atf/test-plugin.js +0 -2
  10. package/dist/atf/test-plugin.js.map +1 -1
  11. package/dist/basic-syntax-plugin.js +0 -1
  12. package/dist/basic-syntax-plugin.js.map +1 -1
  13. package/dist/business-rule-plugin.js +0 -1
  14. package/dist/business-rule-plugin.js.map +1 -1
  15. package/dist/call-expression-plugin.js +0 -1
  16. package/dist/call-expression-plugin.js.map +1 -1
  17. package/dist/claims-plugin.js +0 -1
  18. package/dist/claims-plugin.js.map +1 -1
  19. package/dist/client-script-plugin.js +0 -1
  20. package/dist/client-script-plugin.js.map +1 -1
  21. package/dist/column-plugin.js +24 -7
  22. package/dist/column-plugin.js.map +1 -1
  23. package/dist/cross-scope-privilege-plugin.js +0 -1
  24. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  25. package/dist/dashboard/dashboard-plugin.js +0 -2
  26. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  27. package/dist/data-plugin.js +0 -1
  28. package/dist/data-plugin.js.map +1 -1
  29. package/dist/data-policy-plugin.d.ts +2 -0
  30. package/dist/data-policy-plugin.js +276 -0
  31. package/dist/data-policy-plugin.js.map +1 -0
  32. package/dist/email-notification-plugin.js +2 -3
  33. package/dist/email-notification-plugin.js.map +1 -1
  34. package/dist/flow/flow-logic/flow-logic-constants.d.ts +2 -0
  35. package/dist/flow/flow-logic/flow-logic-constants.js +6 -1
  36. package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -1
  37. package/dist/flow/flow-logic/flow-logic-diagnostics.js +192 -56
  38. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  39. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +2 -1
  40. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +44 -5
  41. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -1
  42. package/dist/flow/flow-logic/flow-logic-plugin.js +279 -29
  43. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  44. package/dist/flow/flow-logic/flow-logic-shapes.d.ts +15 -0
  45. package/dist/flow/flow-logic/flow-logic-shapes.js +25 -1
  46. package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -1
  47. package/dist/flow/plugins/approval-rules-plugin.js +0 -1
  48. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -1
  49. package/dist/flow/plugins/flow-action-definition-plugin.js +804 -205
  50. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  51. package/dist/flow/plugins/flow-data-pill-plugin.js +3 -5
  52. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  53. package/dist/flow/plugins/flow-definition-plugin.js +84 -17
  54. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  55. package/dist/flow/plugins/flow-diagnostics-plugin.js +65 -3
  56. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -1
  57. package/dist/flow/plugins/flow-instance-plugin.js +13 -5
  58. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  59. package/dist/flow/plugins/flow-trigger-instance-plugin.js +0 -1
  60. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
  61. package/dist/flow/plugins/inline-script-plugin.js +0 -1
  62. package/dist/flow/plugins/inline-script-plugin.js.map +1 -1
  63. package/dist/flow/plugins/step-definition-plugin.js +0 -2
  64. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  65. package/dist/flow/plugins/step-instance-plugin.js +216 -77
  66. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  67. package/dist/flow/plugins/trigger-plugin.js +0 -2
  68. package/dist/flow/plugins/trigger-plugin.js.map +1 -1
  69. package/dist/flow/plugins/wfa-datapill-plugin.js +0 -1
  70. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  71. package/dist/flow/utils/datapill-transformer.js +9 -5
  72. package/dist/flow/utils/datapill-transformer.js.map +1 -1
  73. package/dist/flow/utils/flow-constants.d.ts +12 -0
  74. package/dist/flow/utils/flow-constants.js +17 -3
  75. package/dist/flow/utils/flow-constants.js.map +1 -1
  76. package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
  77. package/dist/flow/utils/flow-io-to-record.js +21 -13
  78. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  79. package/dist/flow/utils/flow-pill-utils.d.ts +26 -0
  80. package/dist/flow/utils/flow-pill-utils.js +50 -0
  81. package/dist/flow/utils/flow-pill-utils.js.map +1 -0
  82. package/dist/flow/utils/flow-stage-processor.d.ts +138 -0
  83. package/dist/flow/utils/flow-stage-processor.js +665 -0
  84. package/dist/flow/utils/flow-stage-processor.js.map +1 -0
  85. package/dist/flow/utils/pill-string-parser.js +28 -43
  86. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  87. package/dist/flow/utils/utils.d.ts +11 -6
  88. package/dist/flow/utils/utils.js +37 -28
  89. package/dist/flow/utils/utils.js.map +1 -1
  90. package/dist/form-plugin.js +4 -14
  91. package/dist/form-plugin.js.map +1 -1
  92. package/dist/html-import-plugin.js +0 -1
  93. package/dist/html-import-plugin.js.map +1 -1
  94. package/dist/import-sets-plugin.js +0 -2
  95. package/dist/import-sets-plugin.js.map +1 -1
  96. package/dist/inbound-email-action-plugin.js +0 -1
  97. package/dist/inbound-email-action-plugin.js.map +1 -1
  98. package/dist/index.d.ts +2 -1
  99. package/dist/index.js +5 -1
  100. package/dist/index.js.map +1 -1
  101. package/dist/instance-scan-plugin.js +0 -7
  102. package/dist/instance-scan-plugin.js.map +1 -1
  103. package/dist/json-plugin.js +0 -1
  104. package/dist/json-plugin.js.map +1 -1
  105. package/dist/list-plugin.js +4 -1
  106. package/dist/list-plugin.js.map +1 -1
  107. package/dist/now-attach-plugin.js +0 -1
  108. package/dist/now-attach-plugin.js.map +1 -1
  109. package/dist/now-config-plugin.js +0 -1
  110. package/dist/now-config-plugin.js.map +1 -1
  111. package/dist/now-id-plugin.js +0 -1
  112. package/dist/now-id-plugin.js.map +1 -1
  113. package/dist/now-include-plugin.js +0 -1
  114. package/dist/now-include-plugin.js.map +1 -1
  115. package/dist/now-ref-plugin.js +0 -1
  116. package/dist/now-ref-plugin.js.map +1 -1
  117. package/dist/now-unresolved-plugin.js +0 -1
  118. package/dist/now-unresolved-plugin.js.map +1 -1
  119. package/dist/package-json-plugin.js +3 -2
  120. package/dist/package-json-plugin.js.map +1 -1
  121. package/dist/property-plugin.js +0 -2
  122. package/dist/property-plugin.js.map +1 -1
  123. package/dist/record-plugin.d.ts +2 -0
  124. package/dist/record-plugin.js +2 -2
  125. package/dist/record-plugin.js.map +1 -1
  126. package/dist/repack/lint/Rules.d.ts +1 -2
  127. package/dist/rest-api-plugin.js +6 -5
  128. package/dist/rest-api-plugin.js.map +1 -1
  129. package/dist/role-plugin.js +0 -1
  130. package/dist/role-plugin.js.map +1 -1
  131. package/dist/schedule-script/scheduled-script-plugin.js +5 -4
  132. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
  133. package/dist/script-action-plugin.js +0 -2
  134. package/dist/script-action-plugin.js.map +1 -1
  135. package/dist/script-include-plugin.js +0 -4
  136. package/dist/script-include-plugin.js.map +1 -1
  137. package/dist/server-module-plugin/index.js +2 -3
  138. package/dist/server-module-plugin/index.js.map +1 -1
  139. package/dist/service-catalog/catalog-clientscript-plugin.js +0 -2
  140. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  141. package/dist/service-catalog/catalog-item-plugin.js +0 -2
  142. package/dist/service-catalog/catalog-item-plugin.js.map +1 -1
  143. package/dist/service-catalog/catalog-ui-policy-plugin.js +0 -2
  144. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  145. package/dist/service-catalog/sc-record-producer-plugin.js +0 -2
  146. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
  147. package/dist/service-catalog/variable-set-plugin.js +0 -2
  148. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  149. package/dist/service-portal/angular-provider-plugin.js +0 -2
  150. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  151. package/dist/service-portal/dependency-plugin.js +3 -5
  152. package/dist/service-portal/dependency-plugin.js.map +1 -1
  153. package/dist/service-portal/header-footer-plugin.js +3 -5
  154. package/dist/service-portal/header-footer-plugin.js.map +1 -1
  155. package/dist/service-portal/menu-plugin.js +0 -1
  156. package/dist/service-portal/menu-plugin.js.map +1 -1
  157. package/dist/service-portal/page-plugin.js +0 -1
  158. package/dist/service-portal/page-plugin.js.map +1 -1
  159. package/dist/service-portal/page-route-map-plugin.js +0 -1
  160. package/dist/service-portal/page-route-map-plugin.js.map +1 -1
  161. package/dist/service-portal/portal-plugin.js +0 -2
  162. package/dist/service-portal/portal-plugin.js.map +1 -1
  163. package/dist/service-portal/theme-plugin.js +0 -2
  164. package/dist/service-portal/theme-plugin.js.map +1 -1
  165. package/dist/service-portal/widget-plugin.js +3 -5
  166. package/dist/service-portal/widget-plugin.js.map +1 -1
  167. package/dist/sla-plugin.js +0 -2
  168. package/dist/sla-plugin.js.map +1 -1
  169. package/dist/static-content-plugin.js +32 -3
  170. package/dist/static-content-plugin.js.map +1 -1
  171. package/dist/table-plugin.js +102 -11
  172. package/dist/table-plugin.js.map +1 -1
  173. package/dist/ui-action-plugin.js +26 -17
  174. package/dist/ui-action-plugin.js.map +1 -1
  175. package/dist/ui-page-plugin.js +159 -17
  176. package/dist/ui-page-plugin.js.map +1 -1
  177. package/dist/ui-policy-plugin.js +0 -1
  178. package/dist/ui-policy-plugin.js.map +1 -1
  179. package/dist/user-preference-plugin.js +0 -2
  180. package/dist/user-preference-plugin.js.map +1 -1
  181. package/dist/utils.d.ts +1 -9
  182. package/dist/utils.js +0 -14
  183. package/dist/utils.js.map +1 -1
  184. package/dist/ux-list-menu-config-plugin.js +0 -2
  185. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  186. package/dist/view-plugin.js +0 -1
  187. package/dist/view-plugin.js.map +1 -1
  188. package/dist/workspace-plugin.js +0 -2
  189. package/dist/workspace-plugin.js.map +1 -1
  190. package/package.json +6 -6
  191. package/src/acl-plugin.ts +1 -4
  192. package/src/applicability-plugin.ts +0 -2
  193. package/src/application-menu-plugin.ts +0 -2
  194. package/src/arrow-function-plugin.ts +0 -1
  195. package/src/atf/test-plugin.ts +0 -2
  196. package/src/basic-syntax-plugin.ts +0 -1
  197. package/src/business-rule-plugin.ts +1 -2
  198. package/src/call-expression-plugin.ts +0 -1
  199. package/src/claims-plugin.ts +0 -1
  200. package/src/client-script-plugin.ts +1 -2
  201. package/src/column-plugin.ts +29 -9
  202. package/src/cross-scope-privilege-plugin.ts +1 -2
  203. package/src/dashboard/dashboard-plugin.ts +0 -2
  204. package/src/data-plugin.ts +0 -1
  205. package/src/data-policy-plugin.ts +333 -0
  206. package/src/email-notification-plugin.ts +8 -4
  207. package/src/flow/flow-logic/flow-logic-constants.ts +6 -0
  208. package/src/flow/flow-logic/flow-logic-diagnostics.ts +236 -58
  209. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +59 -6
  210. package/src/flow/flow-logic/flow-logic-plugin.ts +368 -38
  211. package/src/flow/flow-logic/flow-logic-shapes.ts +25 -0
  212. package/src/flow/plugins/approval-rules-plugin.ts +0 -1
  213. package/src/flow/plugins/flow-action-definition-plugin.ts +940 -208
  214. package/src/flow/plugins/flow-data-pill-plugin.ts +3 -5
  215. package/src/flow/plugins/flow-definition-plugin.ts +159 -26
  216. package/src/flow/plugins/flow-diagnostics-plugin.ts +89 -3
  217. package/src/flow/plugins/flow-instance-plugin.ts +26 -12
  218. package/src/flow/plugins/flow-trigger-instance-plugin.ts +0 -1
  219. package/src/flow/plugins/inline-script-plugin.ts +0 -1
  220. package/src/flow/plugins/step-definition-plugin.ts +0 -2
  221. package/src/flow/plugins/step-instance-plugin.ts +259 -65
  222. package/src/flow/plugins/trigger-plugin.ts +0 -2
  223. package/src/flow/plugins/wfa-datapill-plugin.ts +0 -1
  224. package/src/flow/utils/datapill-transformer.ts +13 -5
  225. package/src/flow/utils/flow-constants.ts +19 -1
  226. package/src/flow/utils/flow-io-to-record.ts +29 -19
  227. package/src/flow/utils/flow-pill-utils.ts +48 -0
  228. package/src/flow/utils/flow-stage-processor.ts +831 -0
  229. package/src/flow/utils/pill-string-parser.ts +29 -47
  230. package/src/flow/utils/utils.ts +39 -35
  231. package/src/form-plugin.ts +5 -15
  232. package/src/html-import-plugin.ts +0 -1
  233. package/src/import-sets-plugin.ts +0 -2
  234. package/src/inbound-email-action-plugin.ts +1 -2
  235. package/src/index.ts +7 -1
  236. package/src/instance-scan-plugin.ts +0 -7
  237. package/src/json-plugin.ts +0 -1
  238. package/src/list-plugin.ts +6 -2
  239. package/src/now-attach-plugin.ts +0 -1
  240. package/src/now-config-plugin.ts +0 -1
  241. package/src/now-id-plugin.ts +0 -1
  242. package/src/now-include-plugin.ts +0 -1
  243. package/src/now-ref-plugin.ts +0 -1
  244. package/src/now-unresolved-plugin.ts +0 -1
  245. package/src/package-json-plugin.ts +8 -3
  246. package/src/property-plugin.ts +0 -2
  247. package/src/record-plugin.ts +3 -3
  248. package/src/repack/lint/Rules.ts +1 -1
  249. package/src/rest-api-plugin.ts +7 -6
  250. package/src/role-plugin.ts +1 -2
  251. package/src/schedule-script/scheduled-script-plugin.ts +11 -5
  252. package/src/script-action-plugin.ts +0 -2
  253. package/src/script-include-plugin.ts +0 -4
  254. package/src/server-module-plugin/index.ts +2 -3
  255. package/src/service-catalog/catalog-clientscript-plugin.ts +0 -2
  256. package/src/service-catalog/catalog-item-plugin.ts +0 -2
  257. package/src/service-catalog/catalog-ui-policy-plugin.ts +0 -2
  258. package/src/service-catalog/sc-record-producer-plugin.ts +0 -2
  259. package/src/service-catalog/variable-set-plugin.ts +0 -2
  260. package/src/service-portal/angular-provider-plugin.ts +0 -2
  261. package/src/service-portal/dependency-plugin.ts +0 -2
  262. package/src/service-portal/header-footer-plugin.ts +0 -2
  263. package/src/service-portal/menu-plugin.ts +1 -2
  264. package/src/service-portal/page-plugin.ts +1 -2
  265. package/src/service-portal/page-route-map-plugin.ts +1 -2
  266. package/src/service-portal/portal-plugin.ts +0 -2
  267. package/src/service-portal/theme-plugin.ts +0 -2
  268. package/src/service-portal/widget-plugin.ts +0 -2
  269. package/src/sla-plugin.ts +0 -2
  270. package/src/static-content-plugin.ts +37 -4
  271. package/src/table-plugin.ts +118 -16
  272. package/src/ui-action-plugin.ts +30 -17
  273. package/src/ui-page-plugin.ts +188 -20
  274. package/src/ui-policy-plugin.ts +1 -2
  275. package/src/user-preference-plugin.ts +0 -2
  276. package/src/utils.ts +0 -15
  277. package/src/ux-list-menu-config-plugin.ts +0 -2
  278. package/src/view-plugin.ts +0 -1
  279. package/src/workspace-plugin.ts +0 -2
@@ -1,24 +1,40 @@
1
1
  import {
2
2
  Plugin,
3
3
  CallExpressionShape,
4
+ ObjectShape,
4
5
  type Record,
5
6
  UndefinedShape,
6
7
  type Shape,
7
8
  VariableStatementShape,
8
9
  type Logger,
9
10
  } from '@servicenow/sdk-build-core'
10
- import { FLOW_LOGIC, FLOW_LOGIC_SYS_ID_MAP, FlowLogicSysId, DO_IN_PARALLEL_BLOCK_SYS_ID } from './flow-logic-constants'
11
+ import {
12
+ FLOW_LOGIC,
13
+ FLOW_LOGIC_SYS_ID_MAP,
14
+ FlowLogicSysId,
15
+ DO_IN_PARALLEL_BLOCK_SYS_ID,
16
+ TRY_CATCH_CATCH_SYS_ID,
17
+ } from './flow-logic-constants'
18
+ import { STAGE_API_NAME } from '../utils/flow-constants'
11
19
  import { FlowInstancePlugin } from '../plugins/flow-instance-plugin'
12
20
  import { FlowLogicInstanceShape } from './flow-logic-shapes'
13
21
  import {
14
22
  isConditionalLogicShape,
15
23
  isForEachShape,
16
24
  isDoInParallelShape,
25
+ isTryCatchShape,
17
26
  createEmptyFlowLogicValues,
18
27
  } from './flow-logic-plugin-helpers'
19
28
  import { getFlowLogicArguments, getFlowLogicInstanceRecord } from './flow-logic-plugin-helpers'
20
- import { getRecordFromFlowInstaceShape, sysIdToUuid, getOrder, getDirectVariableIdentifier } from '../utils/utils'
21
- import type { ArrowFunctionShape } from '../../arrow-function-plugin'
29
+ import {
30
+ getRecordFromFlowInstaceShape,
31
+ sysIdToUuid,
32
+ getOrder,
33
+ getOrderSuffix,
34
+ getDirectVariableIdentifier,
35
+ } from '../utils/utils'
36
+ import { ArrowFunctionShape } from '../../arrow-function-plugin'
37
+ import { NowIdShape } from '../../now-id-plugin'
22
38
 
23
39
  /**
24
40
  * FlowLogicPlugin handles flow logic instances (If, ElseIf, Else, GoBackTo) in flow definitions.
@@ -72,7 +88,6 @@ import type { ArrowFunctionShape } from '../../arrow-function-plugin'
72
88
  */
73
89
  export const FlowLogicPlugin = Plugin.create({
74
90
  name: 'FlowLogicPlugin',
75
- docs: [],
76
91
  records: {
77
92
  sys_hub_flow_logic_instance_v2: {
78
93
  relationships: {
@@ -97,12 +112,219 @@ export const FlowLogicPlugin = Plugin.create({
97
112
  if (!logicName || !(logicName in FLOW_LOGIC_SYS_ID_MAP)) {
98
113
  return { success: false }
99
114
  }
100
- const logicBodyShapes: Shape[] = []
115
+
101
116
  const flowSysId = record.get('flow').getValue()
117
+
118
+ // Parallel branches are metadata containers. Collapse them into arrow-function arguments rather than
119
+ // emitting standalone shapes.
120
+ if (logicName === FLOW_LOGIC.DO_IN_PARALLEL) {
121
+ const branchRecords = descendants
122
+ .query('sys_hub_flow_logic_instance_v2', {
123
+ parent_ui_id: record.get('ui_id').getValue(),
124
+ flow: flowSysId,
125
+ logic_definition: DO_IN_PARALLEL_BLOCK_SYS_ID,
126
+ })
127
+ .sort((a, b) => getOrder(a) - getOrder(b))
128
+
129
+ const branchArrows: ArrowFunctionShape[] = []
130
+ for (const branch of branchRecords) {
131
+ const branchUiId = branch.get('ui_id').getValue()
132
+ const branchDescendants = [
133
+ ...descendants.query('sys_hub_sub_flow_instance_v2', {
134
+ parent_ui_id: branchUiId,
135
+ flow: flowSysId,
136
+ }),
137
+ ...descendants.query('sys_hub_action_instance_v2', {
138
+ parent_ui_id: branchUiId,
139
+ flow: flowSysId,
140
+ }),
141
+ ...descendants.query('sys_hub_flow_logic_instance_v2', {
142
+ parent_ui_id: branchUiId,
143
+ flow: flowSysId,
144
+ }),
145
+ ]
146
+ // Catch records share their paired Try's parent (the branch ui_id). They are
147
+ // consumed by the Try's `wfa.flowLogic.tryCatch(...)` toShape via `connected_to`,
148
+ // so calling recordToShape on a Catch here would return `{ success: false }`
149
+ // (its `logic_definition` is not in FLOW_LOGIC_SYS_ID_MAP) and cascade the entire
150
+ // DoInParallel back to raw Records.
151
+ .filter(
152
+ (r) =>
153
+ r.getTable() !== 'sys_hub_flow_logic_instance_v2' ||
154
+ r.get('logic_definition').asString().getValue() !== TRY_CATCH_CATCH_SYS_ID
155
+ )
156
+ .sort((a, b) => getOrder(a) - getOrder(b))
157
+
158
+ const branchStatements: Shape[] = []
159
+ for (const descendant of branchDescendants) {
160
+ const result = await transform.recordToShape(
161
+ descendant,
162
+ database,
163
+ FlowInstancePlugin,
164
+ FlowLogicPlugin
165
+ )
166
+
167
+ if (!result.success) {
168
+ return result
169
+ }
170
+
171
+ branchStatements.push(result.value)
172
+ }
173
+
174
+ branchArrows.push(
175
+ new ArrowFunctionShape({
176
+ source: branch,
177
+ parameters: [],
178
+ statements: branchStatements,
179
+ })
180
+ )
181
+ }
182
+
183
+ const annotation = record.get('comment').asString().getValue()
184
+ const configArg = new ObjectShape({
185
+ source: record,
186
+ properties: {
187
+ ...(annotation ? { annotation } : {}),
188
+ $id: NowIdShape.from(record),
189
+ },
190
+ })
191
+
192
+ return {
193
+ success: true,
194
+ value: new CallExpressionShape({
195
+ source: record,
196
+ callee: FLOW_LOGIC.DO_IN_PARALLEL,
197
+ args: [configArg, ...branchArrows],
198
+ }),
199
+ }
200
+ }
201
+
202
+ // TryCatch is stored as two sibling top-level records linked by `connected_to`. Collapse the Try +
203
+ // paired Catch + each side's descendants into one `wfa.flowLogic.tryCatch(config, { try, catch })` call.
204
+ if (logicName === FLOW_LOGIC.TRY_CATCH) {
205
+ const tryUiId = record.get('ui_id').getValue()
206
+ const catchRecord = database.get('sys_hub_flow_logic_instance_v2', {
207
+ connected_to: tryUiId,
208
+ logic_definition: TRY_CATCH_CATCH_SYS_ID,
209
+ flow: flowSysId,
210
+ })
211
+
212
+ if (!catchRecord) {
213
+ return { success: false }
214
+ }
215
+
216
+ const catchUiId = catchRecord.get('ui_id').getValue()
217
+
218
+ // Try's body descendants. Filter out any sibling Catch records that share the same parent_ui_id;
219
+ // they are consumed by their paired Try sibling and are not part of the Try's body.
220
+ const tryDescendants = [
221
+ ...descendants.query('sys_hub_sub_flow_instance_v2', {
222
+ parent_ui_id: tryUiId,
223
+ flow: flowSysId,
224
+ }),
225
+ ...descendants.query('sys_hub_action_instance_v2', {
226
+ parent_ui_id: tryUiId,
227
+ flow: flowSysId,
228
+ }),
229
+ ...descendants.query('sys_hub_flow_logic_instance_v2', {
230
+ parent_ui_id: tryUiId,
231
+ flow: flowSysId,
232
+ }),
233
+ ]
234
+ .filter(
235
+ (r) =>
236
+ r.getTable() !== 'sys_hub_flow_logic_instance_v2' ||
237
+ r.get('logic_definition').asString().getValue() !== TRY_CATCH_CATCH_SYS_ID
238
+ )
239
+ .sort((a, b) => getOrder(a) - getOrder(b))
240
+
241
+ // Catch's body descendants. The Catch can be a top-level sibling of the Try (not in `descendants`)
242
+ // or a nested sibling inside another block, so query the flat database to handle both uniformly.
243
+ const matchesCatchBody = (r: Record) =>
244
+ r.get('parent_ui_id').getValue() === catchUiId && r.get('flow').getValue() === flowSysId
245
+
246
+ const catchDescendants = [
247
+ ...database.query('sys_hub_sub_flow_instance_v2').filter(matchesCatchBody),
248
+ ...database.query('sys_hub_action_instance_v2').filter(matchesCatchBody),
249
+ ...database.query('sys_hub_flow_logic_instance_v2').filter(matchesCatchBody),
250
+ ].sort((a, b) => getOrder(a) - getOrder(b))
251
+
252
+ const tryStatements: Shape[] = []
253
+ for (const desc of tryDescendants) {
254
+ const result = await transform.recordToShape(
255
+ desc,
256
+ database,
257
+ FlowInstancePlugin,
258
+ FlowLogicPlugin
259
+ )
260
+
261
+ if (!result.success) {
262
+ return result
263
+ }
264
+
265
+ tryStatements.push(result.value)
266
+ }
267
+
268
+ const catchStatements: Shape[] = []
269
+ for (const desc of catchDescendants) {
270
+ const result = await transform.recordToShape(
271
+ desc,
272
+ database,
273
+ FlowInstancePlugin,
274
+ FlowLogicPlugin
275
+ )
276
+
277
+ if (!result.success) {
278
+ return result
279
+ }
280
+
281
+ catchStatements.push(result.value)
282
+ }
283
+
284
+ const tryArrow = new ArrowFunctionShape({
285
+ source: record,
286
+ parameters: [],
287
+ statements: tryStatements,
288
+ })
289
+
290
+ const catchArrow = new ArrowFunctionShape({
291
+ source: catchRecord,
292
+ parameters: [],
293
+ statements: catchStatements,
294
+ })
295
+
296
+ const handlersArg = new ObjectShape({
297
+ source: record,
298
+ properties: {
299
+ try: tryArrow,
300
+ catch: catchArrow,
301
+ },
302
+ })
303
+
304
+ const annotation = record.get('comment').asString().getValue()
305
+ const configArg = new ObjectShape({
306
+ source: record,
307
+ properties: {
308
+ ...(annotation ? { annotation } : {}),
309
+ $id: NowIdShape.from(record),
310
+ },
311
+ })
312
+
313
+ return {
314
+ success: true,
315
+ value: new CallExpressionShape({
316
+ source: record,
317
+ callee: FLOW_LOGIC.TRY_CATCH,
318
+ args: [configArg, handlersArg],
319
+ }),
320
+ }
321
+ }
322
+
323
+ const logicBodyShapes: Shape[] = []
102
324
  const allDescendants = [
103
325
  ...descendants.query('sys_hub_sub_flow_instance_v2', {
104
326
  parent_ui_id: record.get('ui_id').getValue(),
105
- flow: flowSysId, // This is to skip snapshot records
327
+ flow: flowSysId,
106
328
  }),
107
329
  ...descendants.query('sys_hub_action_instance_v2', {
108
330
  parent_ui_id: record.get('ui_id').getValue(),
@@ -113,6 +335,15 @@ export const FlowLogicPlugin = Plugin.create({
113
335
  flow: flowSysId,
114
336
  }),
115
337
  ]
338
+ // Skip Catch records — they share `parent_ui_id` with their paired Try sibling and are consumed
339
+ // by that Try's `wfa.flowLogic.tryCatch(...)` shape via `connected_to`. Including them here would
340
+ // call the standard flow-logic toShape on a Catch sys_id (which isn't in FLOW_LOGIC_SYS_ID_MAP)
341
+ // and cascade a `{ success: false }` up, dropping the entire enclosing flow back to raw Records.
342
+ .filter(
343
+ (r) =>
344
+ r.getTable() !== 'sys_hub_flow_logic_instance_v2' ||
345
+ r.get('logic_definition').asString().getValue() !== TRY_CATCH_CATCH_SYS_ID
346
+ )
116
347
 
117
348
  // Sort descendants by order field to ensure correct statement sequencing
118
349
  allDescendants.sort((a, b) => getOrder(a) - getOrder(b))
@@ -125,12 +356,7 @@ export const FlowLogicPlugin = Plugin.create({
125
356
  logicBodyShapes.push(result.value)
126
357
  }
127
358
 
128
- const flowLogicArguments: Shape<unknown>[] = getFlowLogicArguments(
129
- logicName,
130
- record,
131
- logicBodyShapes,
132
- database
133
- )
359
+ const flowLogicArguments: Shape[] = getFlowLogicArguments(logicName, record, logicBodyShapes, database)
134
360
 
135
361
  const callExpression = new CallExpressionShape({
136
362
  source: record,
@@ -139,7 +365,9 @@ export const FlowLogicPlugin = Plugin.create({
139
365
  })
140
366
 
141
367
  if (logicName === FLOW_LOGIC.WAIT_FOR_A_DURATION) {
142
- const order = record.get('order')?.getValue()
368
+ // A `WaitForADuration` inside a `DoInParallel` branch carries a composite order like `17➛18`;
369
+ // strip the separator so the generated identifier is valid JS.
370
+ const order = getOrderSuffix((record.get('order')?.getValue() as string | undefined) ?? '')
143
371
  const variableName =
144
372
  getDirectVariableIdentifier(record.getOriginalSource()) ?? `waitForDuration_${order}`
145
373
 
@@ -171,14 +399,12 @@ export const FlowLogicPlugin = Plugin.create({
171
399
  return result
172
400
  }
173
401
  const instanceRecord = result.value
174
- const allInstances: Shape[] = []
175
- const functions: ArrowFunctionShape[] = []
176
402
  const relatedRecords: Record[] = []
177
- // Special handling for DoInParallel - create separate flow logic records for each arrow function
403
+
178
404
  if (isDoInParallelShape(expr)) {
179
405
  const parallelFunctions = expr.getParallelFunctions()
180
406
  const mainUiId = instanceRecord.get('ui_id').getValue()
181
- const mainNowId = instanceRecord.getId().getNowIdKey()
407
+ const mainNowIdKey = instanceRecord.getId().getNowIdKey() ?? instanceRecord.getId().getValue()
182
408
 
183
409
  for (let index = 0; index < parallelFunctions.length; index++) {
184
410
  const parallelFunction = parallelFunctions[index]
@@ -188,16 +414,25 @@ export const FlowLogicPlugin = Plugin.create({
188
414
  const statements = parallelFunction
189
415
  .getStatements()
190
416
  .filter((stmt) => !(stmt instanceof UndefinedShape))
417
+
418
+ // Empty `() => {}` branches are intentionally elided. There is no Flow Designer state
419
+ // to represent an empty parallel branch, and emitting one wouldn't round-trip. `index`
420
+ // still advances so non-empty branches keep their `_block_${index}` slot stable across
421
+ // rebuilds even when an earlier branch is empty.
191
422
  if (statements.length === 0) {
192
423
  continue
193
424
  }
194
425
 
195
- const parallelBlockSysId = `${mainNowId}_block_${index}`
426
+ // Deterministic `NowID` so rebuilds produce the same `sys_id`.
427
+ const parallelBlockNowId = new NowIdShape({
428
+ source: parallelFunction,
429
+ id: `${mainNowIdKey}_block_${index}`,
430
+ })
196
431
 
197
432
  const parallelRecord = await factory.createRecord({
198
433
  source: parallelFunction,
199
434
  table: 'sys_hub_flow_logic_instance_v2',
200
- explicitId: parallelBlockSysId,
435
+ explicitId: parallelBlockNowId,
201
436
  properties: {
202
437
  logic_definition: DO_IN_PARALLEL_BLOCK_SYS_ID,
203
438
  values: createEmptyFlowLogicValues(),
@@ -205,40 +440,103 @@ export const FlowLogicPlugin = Plugin.create({
205
440
  })
206
441
 
207
442
  const parallelUiId = sysIdToUuid(parallelRecord.getId().getValue())
208
- const finalParallelRecord = await parallelRecord.merge({
443
+ const finalParallelRecord = parallelRecord.merge({
209
444
  ui_id: parallelUiId,
210
445
  parent_ui_id: mainUiId,
211
446
  })
212
447
 
213
448
  relatedRecords.push(finalParallelRecord)
214
449
 
215
- // Process statements within this parallel function
216
- parallelFunction
217
- .getStatements()
218
- .filter((stmt) => !(stmt instanceof UndefinedShape))
219
- .forEach((stmt) => {
220
- const record = stmt.is(VariableStatementShape)
221
- ? stmt.getInitializer()?.asRecord()
222
- : stmt.asRecord()
223
- if (record) {
224
- relatedRecords.push(record.merge({ parent_ui_id: parallelUiId }))
225
- }
226
- })
450
+ for (const statement of statements) {
451
+ const childRecord = await getRecordFromFlowInstaceShape(statement, transform)
452
+ if (childRecord) {
453
+ relatedRecords.push(mergeParentChildUIID(childRecord, parallelUiId))
454
+ }
455
+ }
456
+ }
457
+
458
+ return { success: true, value: instanceRecord.with(...relatedRecords) }
459
+ }
460
+
461
+ // Special handling for TryCatch — Flow Designer stores TryCatch as two top-level sibling records: a
462
+ // Try (logic_definition = TRY_CATCH) and a paired Catch (logic_definition = TRY_CATCH_CATCH) linked
463
+ // via `connected_to` from Catch → Try.ui_id. Try-body statements have `parent_ui_id = Try.ui_id`;
464
+ // catch-body statements have `parent_ui_id = Catch.ui_id`.
465
+ if (isTryCatchShape(expr)) {
466
+ const tryUiId = instanceRecord.get('ui_id').getValue() as string
467
+ const mainNowId = instanceRecord.getId().getNowIdKey()
468
+ const catchSysId = `${mainNowId}_catch`
469
+
470
+ const catchPartialRecord = await factory.createRecord({
471
+ source: expr,
472
+ table: 'sys_hub_flow_logic_instance_v2',
473
+ explicitId: catchSysId,
474
+ properties: {
475
+ logic_definition: TRY_CATCH_CATCH_SYS_ID,
476
+ values: createEmptyFlowLogicValues(),
477
+ },
478
+ })
479
+
480
+ const catchUiId = sysIdToUuid(catchPartialRecord.getId().getValue())
481
+ const catchRecord = catchPartialRecord.merge({
482
+ ui_id: catchUiId,
483
+ connected_to: tryUiId,
484
+ })
485
+
486
+ const tryStatements =
487
+ expr
488
+ .getTryHandler()
489
+ ?.getStatements()
490
+ .filter((stmt) => !(stmt instanceof UndefinedShape)) ?? []
491
+
492
+ for (const stmt of tryStatements) {
493
+ const childRecord = await getRecordFromFlowInstaceShape(stmt, transform)
494
+ if (childRecord) {
495
+ relatedRecords.push(mergeParentChildUIID(childRecord, tryUiId))
496
+ }
497
+ }
498
+
499
+ // Push Catch *after* the Try-body statements so the flat-order assignment in
500
+ // flow-definition-plugin produces Try → tryBody → Catch → catchBody. Flow Designer
501
+ // uses the `order` field to render the visual flow; emitting Catch at order=Try+1
502
+ // makes FD interleave the Try body under the Catch arm.
503
+ relatedRecords.push(catchRecord)
504
+
505
+ const catchStatements =
506
+ expr
507
+ .getCatchHandler()
508
+ ?.getStatements()
509
+ .filter((stmt) => !(stmt instanceof UndefinedShape)) ?? []
510
+
511
+ for (const stmt of catchStatements) {
512
+ const childRecord = await getRecordFromFlowInstaceShape(stmt, transform)
513
+ if (childRecord) {
514
+ relatedRecords.push(mergeParentChildUIID(childRecord, catchUiId))
515
+ }
227
516
  }
228
517
 
229
518
  return { success: true, value: instanceRecord.with(...relatedRecords) }
230
519
  }
231
520
 
232
521
  // Standard handling for other flow logic types
522
+ const allInstances: Shape[] = []
523
+ const functions: ArrowFunctionShape[] = []
233
524
  if (isConditionalLogicShape(expr) || isForEachShape(expr)) {
234
525
  functions.push(expr.getBody())
235
526
  }
527
+
236
528
  functions.forEach((fn) => {
237
529
  allInstances.push(...(fn.getStatements().filter((stmt) => !(stmt instanceof UndefinedShape)) ?? []))
238
530
  })
239
531
 
240
- // for instances with body, set parent_ui_id for child records
241
- for (const [, v] of (allInstances ?? []).entries()) {
532
+ // For instances with body, set `parent_ui_id` for child records.
533
+ for (const v of allInstances) {
534
+ // Skip wfa.stage() calls — they are handled by the flow definition plugin
535
+ const callExpr = v instanceof VariableStatementShape ? v.getInitializer() : v
536
+ if (callExpr instanceof CallExpressionShape && callExpr.getCallee() === STAGE_API_NAME) {
537
+ continue
538
+ }
539
+
242
540
  const record = await getRecordFromFlowInstaceShape(v, transform)
243
541
 
244
542
  if (record) {
@@ -256,12 +554,12 @@ export const FlowLogicPlugin = Plugin.create({
256
554
  )
257
555
 
258
556
  relatedRecords.push(
259
- updatedRecord.merge({
260
- parent_ui_id: instanceRecord.get('ui_id').getValue(),
261
- })
557
+ mergeParentChildUIID(updatedRecord, instanceRecord.get('ui_id').getValue() as string)
262
558
  )
263
559
  } else {
264
- relatedRecords.push(record.merge({ parent_ui_id: instanceRecord.get('ui_id').getValue() }))
560
+ relatedRecords.push(
561
+ mergeParentChildUIID(record, instanceRecord.get('ui_id').getValue() as string)
562
+ )
265
563
  }
266
564
  }
267
565
  }
@@ -272,6 +570,38 @@ export const FlowLogicPlugin = Plugin.create({
272
570
  ],
273
571
  })
274
572
 
573
+ /**
574
+ * Merges `parent_ui_id` onto a flow-logic child record and propagates the same `parent_ui_id` onto any TryCatch Catch
575
+ * sibling record nested in the child's direct `related` list.
576
+ *
577
+ * Catch records are emitted by the TryCatch toRecord branch as siblings of their paired Try (linked via
578
+ * `connected_to`), but they live in `Try.getRelated()` because `toRecord` can return only one record. When an outer
579
+ * flow-logic walker (forEach, conditional, doInParallel, tryCatch) assigns `parent_ui_id` to the child Try, a plain
580
+ * `record.merge(...)` does not touch the related Catch, leaving its `parent_ui_id` empty.
581
+ *
582
+ * Flow Designer requires the Catch and its paired Try to share the same `parent_ui_id` (they are sibling blocks).
583
+ * This helper propagates the value to any direct-related Catch record.
584
+ *
585
+ * @param record - The child flow-logic record being parented under an outer container. Typically, a Try record
586
+ * returned from a nested TryCatch's `toRecord`, but may be any flow-logic instance record. Its direct `related` list
587
+ * is scanned for Catch siblings; deeper descendants are left untouched (each layer of the walker is responsible for
588
+ * its own children).
589
+ * @param parentUiId - The `ui_id` of the enclosing flow-logic block (the outer forEach / conditional / DoInParallel
590
+ * block / Try / Catch). Set as `parent_ui_id` on `record` and on every direct-related Catch record.
591
+ * @returns A new `Record` with `parent_ui_id` applied to the top-level record and to any direct-related Catch
592
+ * siblings; all other related records are preserved unchanged.
593
+ */
594
+ function mergeParentChildUIID(record: Record, parentUiId: string): Record {
595
+ const related = record.getRelated()
596
+ const updatedRelated = related.map((r) => {
597
+ if (r.get('logic_definition')?.getValue() === TRY_CATCH_CATCH_SYS_ID) {
598
+ return r.merge({ parent_ui_id: parentUiId })
599
+ }
600
+ return r
601
+ })
602
+ return record.merge({ parent_ui_id: parentUiId }).with(...updatedRelated)
603
+ }
604
+
275
605
  /**
276
606
  * Recursively updates data pill references in forEach loop records
277
607
  *
@@ -130,6 +130,29 @@ export class SetFlowVariablesInstanceShape extends FlowLogicInstanceShape {
130
130
  }
131
131
  }
132
132
 
133
+ /**
134
+ * Shape class for AppendToFlowVariables logic.
135
+ * Handles the pattern: AppendToFlowVariables(config, variables, values)
136
+ *
137
+ * @example
138
+ * AppendToFlowVariables(
139
+ * { $id: Now.ID['append_1'] },
140
+ * params.flowVariables,
141
+ * { objects: { name: 'item' } }
142
+ * )
143
+ */
144
+ export class AppendToFlowVariablesInstanceShape extends FlowLogicInstanceShape {
145
+ getValuesToAppend(): ObjectShape {
146
+ return this.getArgument(2)?.as(ObjectShape)
147
+ }
148
+
149
+ getValuesToAppendAsString(): string {
150
+ const variables = this.getValuesToAppend()
151
+ const entries = variables.entries({ resolve: false })
152
+ return Array.from(entries, ([key]) => key).join(',')
153
+ }
154
+ }
155
+
133
156
  /**
134
157
  * Shape class for AssignSubflowOutputs logic.
135
158
  * Handles the pattern: AssignSubflowOutputs(config, outputs)
@@ -200,6 +223,8 @@ export function createFlowLogicInstanceShape(expr: Source, callee: string, args:
200
223
  return new DoInParallelInstanceShape(expr, callee, args)
201
224
  case FLOW_LOGIC.SET_FLOW_VARIABLES:
202
225
  return new SetFlowVariablesInstanceShape(expr, callee, args)
226
+ case FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES:
227
+ return new AppendToFlowVariablesInstanceShape(expr, callee, args)
203
228
  case FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS:
204
229
  return new AssignSubflowOutputsInstanceShape(expr, callee, args)
205
230
  case FLOW_LOGIC.WAIT_FOR_A_DURATION:
@@ -5,7 +5,6 @@ import { getCallExpressionName } from '../../utils'
5
5
 
6
6
  export const ApprovalRulesPlugin = Plugin.create({
7
7
  name: 'ApprovalRulesPlugin',
8
- docs: [],
9
8
  nodes: [
10
9
  {
11
10
  node: 'CallExpression',