@servicenow/sdk-build-plugins 4.5.0 → 4.6.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 (144) hide show
  1. package/dist/column-plugin.js +3 -7
  2. package/dist/column-plugin.js.map +1 -1
  3. package/dist/flow/flow-logic/flow-logic-diagnostics.js +5 -5
  4. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  5. package/dist/flow/plugins/flow-action-definition-plugin.js +1229 -54
  6. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  7. package/dist/flow/plugins/flow-data-pill-plugin.js +5 -2
  8. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  9. package/dist/flow/plugins/flow-definition-plugin.js +16 -42
  10. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  11. package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +2 -2
  12. package/dist/flow/plugins/flow-diagnostics-plugin.js +2 -2
  13. package/dist/flow/plugins/flow-instance-plugin.js +68 -22
  14. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  15. package/dist/flow/plugins/step-definition-plugin.js +2 -1
  16. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  17. package/dist/flow/plugins/step-instance-plugin.d.ts +9 -1
  18. package/dist/flow/plugins/step-instance-plugin.js +649 -136
  19. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  20. package/dist/flow/plugins/wfa-datapill-plugin.js +20 -5
  21. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  22. package/dist/flow/post-install.js +1 -0
  23. package/dist/flow/post-install.js.map +1 -1
  24. package/dist/flow/utils/complex-object-resolver.js +4 -1
  25. package/dist/flow/utils/complex-object-resolver.js.map +1 -1
  26. package/dist/flow/utils/complex-objects.js +1 -1
  27. package/dist/flow/utils/complex-objects.js.map +1 -1
  28. package/dist/flow/utils/flow-constants.d.ts +66 -2
  29. package/dist/flow/utils/flow-constants.js +402 -6
  30. package/dist/flow/utils/flow-constants.js.map +1 -1
  31. package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
  32. package/dist/flow/utils/flow-io-to-record.js +37 -16
  33. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  34. package/dist/flow/utils/flow-shapes.js +4 -0
  35. package/dist/flow/utils/flow-shapes.js.map +1 -1
  36. package/dist/flow/utils/label-cache-parser.d.ts +9 -2
  37. package/dist/flow/utils/label-cache-parser.js +32 -4
  38. package/dist/flow/utils/label-cache-parser.js.map +1 -1
  39. package/dist/flow/utils/pill-shape-helpers.d.ts +15 -0
  40. package/dist/flow/utils/pill-shape-helpers.js +35 -0
  41. package/dist/flow/utils/pill-shape-helpers.js.map +1 -0
  42. package/dist/flow/utils/pill-string-parser.js +1 -0
  43. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  44. package/dist/flow/utils/schema-to-flow-object.d.ts +6 -1
  45. package/dist/flow/utils/schema-to-flow-object.js +131 -15
  46. package/dist/flow/utils/schema-to-flow-object.js.map +1 -1
  47. package/dist/flow/utils/utils.d.ts +1 -0
  48. package/dist/flow/utils/utils.js +6 -1
  49. package/dist/flow/utils/utils.js.map +1 -1
  50. package/dist/form-plugin.js +7 -9
  51. package/dist/form-plugin.js.map +1 -1
  52. package/dist/inbound-email-action-plugin.d.ts +10 -0
  53. package/dist/inbound-email-action-plugin.js +128 -0
  54. package/dist/inbound-email-action-plugin.js.map +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/instance-scan-plugin.js +0 -5
  59. package/dist/instance-scan-plugin.js.map +1 -1
  60. package/dist/property-plugin.js +1 -1
  61. package/dist/property-plugin.js.map +1 -1
  62. package/dist/record-plugin.d.ts +7 -0
  63. package/dist/record-plugin.js +10 -2
  64. package/dist/record-plugin.js.map +1 -1
  65. package/dist/rest-api-plugin.js +8 -1
  66. package/dist/rest-api-plugin.js.map +1 -1
  67. package/dist/schedule-script/scheduled-script-plugin.js +8 -3
  68. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
  69. package/dist/service-catalog/service-catalog-base.d.ts +18 -18
  70. package/dist/service-catalog/service-catalog-base.js +22 -22
  71. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  72. package/dist/service-portal/header-footer-plugin.d.ts +2 -0
  73. package/dist/service-portal/header-footer-plugin.js +50 -0
  74. package/dist/service-portal/header-footer-plugin.js.map +1 -0
  75. package/dist/service-portal/menu-plugin.js +3 -22
  76. package/dist/service-portal/menu-plugin.js.map +1 -1
  77. package/dist/service-portal/page-plugin.js +3 -24
  78. package/dist/service-portal/page-plugin.js.map +1 -1
  79. package/dist/service-portal/page-route-map-plugin.d.ts +2 -0
  80. package/dist/service-portal/page-route-map-plugin.js +114 -0
  81. package/dist/service-portal/page-route-map-plugin.js.map +1 -0
  82. package/dist/service-portal/portal-plugin.js +21 -8
  83. package/dist/service-portal/portal-plugin.js.map +1 -1
  84. package/dist/service-portal/utils.d.ts +40 -2
  85. package/dist/service-portal/utils.js +283 -2
  86. package/dist/service-portal/utils.js.map +1 -1
  87. package/dist/service-portal/widget-plugin.js +9 -218
  88. package/dist/service-portal/widget-plugin.js.map +1 -1
  89. package/dist/static-content-plugin.js +4 -0
  90. package/dist/static-content-plugin.js.map +1 -1
  91. package/dist/table-plugin.js +190 -26
  92. package/dist/table-plugin.js.map +1 -1
  93. package/dist/ui-action-plugin.js +1 -4
  94. package/dist/ui-action-plugin.js.map +1 -1
  95. package/dist/ui-page-plugin.js +68 -13
  96. package/dist/ui-page-plugin.js.map +1 -1
  97. package/dist/view-plugin.js +8 -3
  98. package/dist/view-plugin.js.map +1 -1
  99. package/dist/workspace-plugin.js +39 -36
  100. package/dist/workspace-plugin.js.map +1 -1
  101. package/package.json +5 -4
  102. package/src/column-plugin.ts +3 -8
  103. package/src/flow/flow-logic/flow-logic-diagnostics.ts +5 -6
  104. package/src/flow/plugins/flow-action-definition-plugin.ts +1581 -61
  105. package/src/flow/plugins/flow-data-pill-plugin.ts +5 -2
  106. package/src/flow/plugins/flow-definition-plugin.ts +12 -47
  107. package/src/flow/plugins/flow-diagnostics-plugin.ts +2 -2
  108. package/src/flow/plugins/flow-instance-plugin.ts +98 -22
  109. package/src/flow/plugins/step-definition-plugin.ts +2 -1
  110. package/src/flow/plugins/step-instance-plugin.ts +772 -156
  111. package/src/flow/plugins/wfa-datapill-plugin.ts +25 -5
  112. package/src/flow/post-install.ts +1 -0
  113. package/src/flow/utils/complex-object-resolver.ts +4 -1
  114. package/src/flow/utils/complex-objects.ts +1 -1
  115. package/src/flow/utils/flow-constants.ts +421 -5
  116. package/src/flow/utils/flow-io-to-record.ts +43 -17
  117. package/src/flow/utils/flow-shapes.ts +4 -0
  118. package/src/flow/utils/label-cache-parser.ts +33 -4
  119. package/src/flow/utils/pill-shape-helpers.ts +42 -0
  120. package/src/flow/utils/pill-string-parser.ts +1 -0
  121. package/src/flow/utils/schema-to-flow-object.ts +183 -15
  122. package/src/flow/utils/utils.ts +12 -1
  123. package/src/form-plugin.ts +1 -3
  124. package/src/inbound-email-action-plugin.ts +145 -0
  125. package/src/index.ts +4 -0
  126. package/src/instance-scan-plugin.ts +0 -5
  127. package/src/property-plugin.ts +4 -1
  128. package/src/record-plugin.ts +14 -4
  129. package/src/rest-api-plugin.ts +7 -1
  130. package/src/schedule-script/scheduled-script-plugin.ts +14 -3
  131. package/src/service-catalog/service-catalog-base.ts +22 -22
  132. package/src/service-portal/header-footer-plugin.ts +57 -0
  133. package/src/service-portal/menu-plugin.ts +1 -23
  134. package/src/service-portal/page-plugin.ts +3 -28
  135. package/src/service-portal/page-route-map-plugin.ts +124 -0
  136. package/src/service-portal/portal-plugin.ts +33 -10
  137. package/src/service-portal/utils.ts +404 -3
  138. package/src/service-portal/widget-plugin.ts +14 -290
  139. package/src/static-content-plugin.ts +3 -0
  140. package/src/table-plugin.ts +226 -36
  141. package/src/ui-action-plugin.ts +1 -8
  142. package/src/ui-page-plugin.ts +76 -13
  143. package/src/view-plugin.ts +10 -4
  144. package/src/workspace-plugin.ts +43 -43
@@ -1,16 +1,43 @@
1
1
  import {
2
2
  Plugin,
3
3
  CallExpressionShape,
4
+ DurationShape,
5
+ IdentifierShape,
4
6
  ObjectShape,
7
+ PropertyAccessShape,
8
+ TemplateExpressionShape,
9
+ TemplateValueShape,
5
10
  type Record as BuildRecord,
6
11
  type Factory,
7
12
  type Shape,
8
13
  type Source,
14
+ type Diagnostics,
9
15
  } from '@servicenow/sdk-build-core'
10
- import type { NowIdShape } from '../../now-id-plugin'
11
- import { sysIdToUuid } from '../utils/utils'
16
+ import { NowIdShape } from '../../now-id-plugin'
17
+ import { getBuiltInStepIdentifier, sysIdToUuid } from '../utils/utils'
12
18
  import { COLUMN_API_TO_TYPE } from '../../column/column-helper'
13
- import { ACTION_STEP_INSTANCE_API_NAME } from '../utils/flow-constants'
19
+ import {
20
+ ACTION_STEP_INSTANCE_API_NAME,
21
+ BUILT_IN_STEP_DEFINITIONS,
22
+ BUILT_IN_STEP_NAME_TO_SYSID,
23
+ BUILT_IN_STEP_PREFIX,
24
+ buildExtVarAttributes,
25
+ getVarEntryName,
26
+ getVarEntryType,
27
+ FLOW_OBJECT_API_NAME,
28
+ FLOW_ARRAY_API_NAME,
29
+ } from '../utils/flow-constants'
30
+ import { normalizeInputValue } from './flow-instance-plugin'
31
+ import { ApprovalRulesShape, ApprovalDueDateShape } from '../utils/flow-shapes'
32
+ import { createSdkDocEntry } from '../../utils'
33
+ import {
34
+ generateSchemaFromObject,
35
+ getComplexObjectAttributes,
36
+ formatComplexObjectAttributes,
37
+ } from '../utils/complex-objects'
38
+
39
+ import { PillShape } from '../utils/data-pill-shapes'
40
+ import { findAncestorByCalleeName } from '../utils/utils'
14
41
 
15
42
  // Error handling type mapping: string to number
16
43
  const ERROR_HANDLING_TYPE_MAP: Record<string, number> = {
@@ -18,6 +45,148 @@ const ERROR_HANDLING_TYPE_MAP: Record<string, number> = {
18
45
  dont_stop_the_action: 2,
19
46
  }
20
47
 
48
+ /**
49
+ * Resolves a TemplateValue field shape to a datapill string (e.g., "{{action.desc|string}}").
50
+ * Handles two cases:
51
+ * 1. Already resolved PillShape — call getValue() directly
52
+ * 2. Unresolved wfa.dataPill() CallExpressionShape — extract pill info from arguments
53
+ * @param cidMap - Optional map of step variable name → CID for direct lookup (avoids AST walking)
54
+ * Returns undefined if the field is not a datapill.
55
+ */
56
+ function resolveFieldToPillString(fieldShape: Shape, cidMap?: Map<string, string>): string | undefined {
57
+ // Case 1: Already a PillShape (e.g., processed by the datapill plugin)
58
+ if (fieldShape instanceof PillShape) {
59
+ return fieldShape.getValue()
60
+ }
61
+
62
+ // Case 2: Unresolved wfa.dataPill(expression, type) CallExpressionShape
63
+ if (fieldShape instanceof CallExpressionShape) {
64
+ const callee = fieldShape.getCallee()
65
+ if (callee !== 'wfa.dataPill') {
66
+ return undefined
67
+ }
68
+
69
+ const typeArg = fieldShape.getArgument(1)
70
+
71
+ // Extract property path — try Shape API first, then AST node directly
72
+ const propertyNames: string[] = []
73
+ const expressionArg = fieldShape.getArgument(0, false) // 0, false
74
+ if (expressionArg instanceof PropertyAccessShape) {
75
+ expressionArg.getElements().forEach((el: Shape) => {
76
+ if (el instanceof IdentifierShape) {
77
+ propertyNames.push(el.getName())
78
+ }
79
+ })
80
+ }
81
+
82
+ if (propertyNames.length < 2) {
83
+ return undefined
84
+ }
85
+
86
+ let prefix = 'action'
87
+ let pathParts: string[]
88
+
89
+ if (propertyNames[1] === 'inputs') {
90
+ // params.inputs.xxx → action pill
91
+ const originalNode = fieldShape.getOriginalNode?.()
92
+ if (originalNode) {
93
+ const actionAncestor = findAncestorByCalleeName(originalNode, 'Action')
94
+ prefix = actionAncestor ? 'action' : 'subflow'
95
+ }
96
+ pathParts = propertyNames.slice(2)
97
+ } else {
98
+ // Could be a step variable reference: create_record_step.record.short_description
99
+ // Resolve CID via cidMap lookup (O(1))
100
+ const varName = propertyNames[0]
101
+ const stepCid = varName ? cidMap?.get(varName) : undefined
102
+ if (stepCid) {
103
+ prefix = `step[${stepCid}]`
104
+ pathParts = propertyNames.slice(1)
105
+ } else {
106
+ // Cannot resolve step CID — return undefined so the caller can handle the failure
107
+ return undefined
108
+ }
109
+ }
110
+
111
+ const dataType = typeArg?.ifString()?.getValue()
112
+ const typeSuffix = dataType ? `|${dataType}` : ''
113
+
114
+ return `{{${prefix}.${pathParts.join('.')}${typeSuffix}}}`
115
+ }
116
+
117
+ // Case 3: Already a resolved pill string
118
+ const val = fieldShape.getValue()
119
+ if (typeof val === 'string' && val.startsWith('{{') && val.endsWith('}}')) {
120
+ return val
121
+ }
122
+
123
+ return undefined
124
+ }
125
+
126
+ /**
127
+ * Extracts pill type annotations from a pill string and stores them in the pillTypeMap.
128
+ * Captures types from both step pills ({{step[CID].path|type}}) and action pills
129
+ * ({{action.path|type}}). The "|type" portion is captured before it gets stripped
130
+ * for the element_mapping value. The collected types are used later when building the label_cache.
131
+ */
132
+ const STEP_PILL_TYPE_REGEX = /\{\{step\[([^\]]+)\]\.([^|}]+)(?:\|([^}]+))?\}\}/g
133
+ const ACTION_PILL_TYPE_REGEX = /\{\{action\.([^|}]+)(?:\|([^}]+))?\}\}/g
134
+ function collectPillTypes(pillString: string, pillTypeMap?: Map<string, string>): void {
135
+ if (!pillTypeMap) {
136
+ return
137
+ }
138
+ for (const match of pillString.matchAll(STEP_PILL_TYPE_REGEX)) {
139
+ const [, cid, pillPath, dataType] = match
140
+ if (cid && pillPath && dataType) {
141
+ pillTypeMap.set(`${cid}::${pillPath}`, dataType)
142
+ }
143
+ }
144
+ for (const match of pillString.matchAll(ACTION_PILL_TYPE_REGEX)) {
145
+ const [, path, dataType] = match
146
+ if (path && dataType) {
147
+ pillTypeMap.set(`action::${path}`, dataType)
148
+ }
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Strips the |type suffix from a pill string.
154
+ * e.g., "{{action.desc|string}}" → "{{action.desc}}"
155
+ *
156
+ * Note: This is called on individual pill strings (from resolveFieldToPillString),
157
+ * never on concatenated multi-pill strings — so no `g` flag is needed.
158
+ */
159
+ function stripPillType(pill: string): string {
160
+ return pill.replace(/\|[^}]+/, '')
161
+ }
162
+
163
+ /**
164
+ * Resolves a TemplateExpressionShape into a concatenated string, replacing
165
+ * datapill spans with their pill strings (type-stripped) and collecting pill types.
166
+ * Returns the resolved string and whether any pills were found.
167
+ */
168
+ function resolveTemplateExpression(
169
+ templateShape: TemplateExpressionShape,
170
+ cidMap?: Map<string, string>,
171
+ pillTypeMap?: Map<string, string>
172
+ ): { result: string; hasPills: boolean } {
173
+ let result = templateShape.getLiteralText()
174
+ let hasPills = false
175
+ for (const span of templateShape.getSpans()) {
176
+ const expr = span.getExpression()
177
+ const pillStr = resolveFieldToPillString(expr as Shape, cidMap)
178
+ if (pillStr) {
179
+ collectPillTypes(pillStr, pillTypeMap)
180
+ result += stripPillType(pillStr)
181
+ hasPills = true
182
+ } else {
183
+ result += String(expr.getValue?.() ?? '')
184
+ }
185
+ result += span.getLiteralText()
186
+ }
187
+ return { result, hasPills }
188
+ }
189
+
21
190
  /**
22
191
  * StepInstanceShape represents a step instance within a custom action's body
23
192
  * This class handles the shape transformation for step instances
@@ -29,6 +198,10 @@ export class StepInstanceShape extends CallExpressionShape {
29
198
  private stepLabel: string
30
199
  private inputs: ObjectShape | undefined
31
200
  private inputDefinitions: ObjectShape | undefined
201
+ /** Map of step variable name → CID, set by ActionDefinitionPlugin for step pill resolution in Action() bodies */
202
+ private _cidMap?: Map<string, string>
203
+ /** Map of "CID::pillPath" → dataType, populated during toRecord for label_cache type propagation */
204
+ private _pillTypeMap?: Map<string, string>
32
205
 
33
206
  constructor(args: {
34
207
  source: Source
@@ -41,7 +214,7 @@ export class StepInstanceShape extends CallExpressionShape {
41
214
  inputDefinitions?: ObjectShape
42
215
  }) {
43
216
  const { source, sysId, config, stepDefinitionSysId, stepLabel, inputs, inputDefinitions } = args
44
- super({ source, callee: 'StepInstance', args: [sysId, config, stepDefinitionSysId] })
217
+ super({ source, callee: 'wfa.actionStep', args: [sysId, config, stepDefinitionSysId] })
45
218
  this.config = config
46
219
  this.sysId = sysId
47
220
  this.stepDefinitionSysId = stepDefinitionSysId
@@ -73,11 +246,27 @@ export class StepInstanceShape extends CallExpressionShape {
73
246
  getInputDefinitions(): ObjectShape | undefined {
74
247
  return this.inputDefinitions
75
248
  }
249
+
250
+ setCidMap(cidMap: Map<string, string>): void {
251
+ this._cidMap = cidMap
252
+ }
253
+
254
+ getCidMap(): Map<string, string> | undefined {
255
+ return this._cidMap
256
+ }
257
+
258
+ setPillTypeMap(pillTypeMap: Map<string, string>): void {
259
+ this._pillTypeMap = pillTypeMap
260
+ }
261
+
262
+ getPillTypeMap(): Map<string, string> | undefined {
263
+ return this._pillTypeMap
264
+ }
76
265
  }
77
266
 
78
267
  export const StepInstancePlugin = Plugin.create({
79
268
  name: 'StepInstancePlugin',
80
- docs: [],
269
+ docs: [createSdkDocEntry('wfa.actionStep', ['sys_hub_step_instance'])],
81
270
  shapes: [
82
271
  {
83
272
  shape: CallExpressionShape,
@@ -99,12 +288,8 @@ export const StepInstancePlugin = Plugin.create({
99
288
 
100
289
  const [stepDefinition, stepInstanceConfig, inputs] = args
101
290
 
102
- // Validate first argument is a StepDefinition
103
- if (
104
- !(stepDefinition instanceof CallExpressionShape) ||
105
- stepDefinition.getCallee() !== 'ActionStepDefinition'
106
- ) {
107
- diagnostics.error(callExpression, 'Expected first argument to be a ActionStepDefinition')
291
+ if (!stepDefinition) {
292
+ diagnostics.error(callExpression, 'Missing step definition argument')
108
293
  return { success: false }
109
294
  }
110
295
 
@@ -120,7 +305,51 @@ export const StepInstancePlugin = Plugin.create({
120
305
  return { success: false }
121
306
  }
122
307
 
123
- const stepConfig = stepDefinition.getArgument(0).asObject()
308
+ let stepDefinitionSysId: string
309
+ let stepLabel: string
310
+ let inputDefinitions: ObjectShape | undefined
311
+
312
+ // First argument can be a string (built-in step name), IdentifierShape (actionStep.xxx),
313
+ // or ActionStepDefinition() call
314
+ const stepNameValue = stepDefinition.ifString()?.getValue()
315
+ if (stepNameValue) {
316
+ // Built-in step referenced by name string
317
+ stepDefinitionSysId = BUILT_IN_STEP_NAME_TO_SYSID[stepNameValue] ?? ''
318
+ stepLabel = stepNameValue
319
+ if (!stepDefinitionSysId) {
320
+ diagnostics.error(callExpression, `Unknown built-in step type '${stepNameValue}'`)
321
+ return { success: false }
322
+ }
323
+ } else if (stepDefinition instanceof IdentifierShape) {
324
+ // Built-in step referenced via actionStep.xxx identifier
325
+ const identifierName = stepDefinition.getName()
326
+ const prefix = `${BUILT_IN_STEP_PREFIX}.`
327
+ const resolvedName = identifierName.startsWith(prefix)
328
+ ? identifierName.slice(prefix.length)
329
+ : identifierName
330
+ stepDefinitionSysId = BUILT_IN_STEP_NAME_TO_SYSID[resolvedName] ?? ''
331
+ stepLabel = resolvedName
332
+ if (!stepDefinitionSysId) {
333
+ diagnostics.error(callExpression, `Unknown built-in step type '${identifierName}'`)
334
+ return { success: false }
335
+ }
336
+ } else if (
337
+ stepDefinition instanceof CallExpressionShape &&
338
+ stepDefinition.getCallee() === 'ActionStepDefinition'
339
+ ) {
340
+ // Custom step definition
341
+ const stepConfig = stepDefinition.getArgument(0).asObject()
342
+ stepDefinitionSysId = stepConfig.get('$id')?.getValue() as string
343
+ stepLabel = stepConfig.get('name')?.getValue() as string
344
+ inputDefinitions = stepConfig.get('inputs')?.ifObject()?.asObject()
345
+ } else {
346
+ diagnostics.error(
347
+ callExpression,
348
+ 'Expected first argument to be a step type name string, actionStep.xxx identifier, or ActionStepDefinition'
349
+ )
350
+ return { success: false }
351
+ }
352
+
124
353
  const shapeArgs: {
125
354
  source: Source
126
355
  config: ObjectShape
@@ -133,10 +362,10 @@ export const StepInstancePlugin = Plugin.create({
133
362
  source: callExpression,
134
363
  sysId: stepInstanceConfig.get('$id')?.getValue() as NowIdShape | Shape,
135
364
  config: stepInstanceConfig,
136
- stepDefinitionSysId: stepConfig.get('$id')?.getValue() as string,
137
- stepLabel: stepConfig.get('name')?.getValue() as string,
138
- inputDefinitions: stepConfig.get('inputs')?.asObject(),
365
+ stepDefinitionSysId,
366
+ stepLabel,
139
367
  inputs: inputs,
368
+ ...(inputDefinitions ? { inputDefinitions } : {}),
140
369
  }
141
370
 
142
371
  return {
@@ -148,10 +377,11 @@ export const StepInstancePlugin = Plugin.create({
148
377
  {
149
378
  shape: StepInstanceShape,
150
379
  fileTypes: ['fluent'],
151
- async toRecord(callExpression, { factory }) {
380
+ async toRecord(callExpression, { factory, diagnostics }) {
152
381
  const stepInstanceRecord = await buildStepInstanceRecord({
153
382
  callExpression,
154
383
  factory,
384
+ diagnostics,
155
385
  values: callExpression.getInputs() as ObjectShape,
156
386
  sysId: callExpression.getSysId(),
157
387
  stepDefinitionSysId: callExpression.getStepDefinitionSysId(),
@@ -169,6 +399,12 @@ export const StepInstancePlugin = Plugin.create({
169
399
  sys_hub_step_ext_output: {
170
400
  coalesce: ['model', 'element'],
171
401
  },
402
+ sys_complex_object: {
403
+ coalesce: ['name'],
404
+ },
405
+ sys_element_mapping: {
406
+ coalesce: ['field', 'id'],
407
+ },
172
408
  sys_hub_step_instance: {
173
409
  relationships: {
174
410
  sys_variable_value: {
@@ -192,87 +428,137 @@ export const StepInstancePlugin = Plugin.create({
192
428
  descendant: true,
193
429
  },
194
430
  },
195
- // toShape(record, { descendants, diagnostics }) {
196
- // // Get the step definition reference
197
- // const stepDefinitionRecords = descendants.query('sys_flow_step_definition')
198
- // if (stepDefinitionRecords.length === 0) {
199
- // diagnostics.error(record, 'Could not find step definition for step instance')
200
- // return { success: false }
201
- // }
202
-
203
- // const stepDefinition = stepDefinitionRecords[0]
204
- // if (!stepDefinition) {
205
- // diagnostics.error(record, 'Step definition is undefined')
206
- // return { success: false }
207
- // }
208
-
209
- // // Build the config object from sys_variable_value records
210
- // const variableValues = descendants.query('sys_variable_value')
211
- // const configObj: globalThis.Record<string, any> = {}
212
-
213
- // for (const varValue of variableValues) {
214
- // const variableSysId = varValue.get('variable')?.asString()?.getValue()
215
- // const value = varValue.get('value')?.asString()?.getValue()
216
-
217
- // // Find the matching input definition to get the element name
218
- // const inputRecords = descendants.query('sys_flow_step_definition_input', { sysId: variableSysId })
219
- // if (inputRecords.length > 0) {
220
- // const elementName = inputRecords[0]?.get('element')?.asString()?.getValue()
221
- // if (elementName) {
222
- // // Try to parse JSON values, otherwise use as string
223
- // try {
224
- // configObj[elementName] = JSON.parse(value || '')
225
- // } catch {
226
- // configObj[elementName] = value
227
- // }
228
- // }
229
- // }
230
- // }
231
-
232
- // // Add error_handling_type and label from the step instance record itself
233
- // const errorHandlingType = record.get('error_handling_type')?.asNumber()?.getValue()
234
- // if (errorHandlingType !== undefined && errorHandlingType !== 1) {
235
- // configObj['error_handling_type'] = errorHandlingType
236
- // }
237
-
238
- // const label = record.get('label')?.asString()?.getValue()
239
- // const stepName = stepDefinition.get('name')?.asString()?.getValue()
240
- // if (label && label !== stepName) {
241
- // configObj['label'] = label
242
- // }
243
-
244
- // // Create the StepDefinition CallExpressionShape reference
245
- // const stepDefinitionShape = new CallExpressionShape({
246
- // source: stepDefinition,
247
- // callee: 'StepDefinition',
248
- // args: [
249
- // stepDefinition.transform(({ $ }) => ({
250
- // $id: $.val(NowIdShape.from(stepDefinition)),
251
- // name: $,
252
- // })),
253
- // ],
254
- // })
255
-
256
- // // Create the config ObjectShape
257
- // const configProperties: globalThis.Record<string, any> = {}
258
- // for (const [key, value] of Object.entries(configObj)) {
259
- // configProperties[key] = value
260
- // }
261
-
262
- // const configShape = new ObjectShape({
263
- // source: record,
264
- // properties: configProperties,
265
- // })
266
-
267
- // return {
268
- // success: true,
269
- // value: new CallExpressionShape({
270
- // source: record,
271
- // callee: 'StepInstance',
272
- // args: [stepDefinitionShape, NowIdShape.from(record), configShape],
273
- // }),
274
- // }
275
- // },
431
+ toShape(record, { descendants, logger }) {
432
+ // Skip step instances that belong to an action definition
433
+ // those are handled by ActionDefinitionPlugin's toShape for Action() definitions
434
+ const actionRef = record.get('action')?.asString()?.getValue()
435
+ if (actionRef) {
436
+ return { success: false }
437
+ }
438
+
439
+ const stepDefinitionRecords = descendants.query('sys_flow_step_definition')
440
+ const variableValues = descendants.query('sys_variable_value')
441
+ const configObj: globalThis.Record<string, unknown> = {}
442
+
443
+ let stepDefinitionShape: CallExpressionShape | IdentifierShape | string
444
+ let stepName: string | undefined
445
+
446
+ if (stepDefinitionRecords.length > 0) {
447
+ // Custom step definition found in descendants
448
+ const stepDefinition = stepDefinitionRecords[0]
449
+ if (!stepDefinition) {
450
+ logger.warn('Step definition is undefined')
451
+ return { success: false }
452
+ }
453
+
454
+ stepName = stepDefinition.get('name')?.asString()?.getValue()
455
+
456
+ // Resolve variable values using input definition records from descendants
457
+ for (const varValue of variableValues) {
458
+ const variableSysId = varValue.get('variable')?.asString()?.getValue()
459
+ const value = varValue.get('value')?.asString()?.getValue()
460
+
461
+ const inputRecords = descendants.query('sys_flow_step_definition_input', {
462
+ sysId: variableSysId,
463
+ })
464
+ if (inputRecords.length > 0) {
465
+ const elementName = inputRecords[0]?.get('element')?.asString()?.getValue()
466
+ if (elementName) {
467
+ try {
468
+ configObj[elementName] = JSON.parse(value || '')
469
+ } catch {
470
+ configObj[elementName] = value
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+ stepDefinitionShape = new CallExpressionShape({
477
+ source: stepDefinition,
478
+ callee: 'ActionStepDefinition',
479
+ args: [
480
+ stepDefinition.transform(({ $ }) => ({
481
+ $id: $.val(NowIdShape.from(stepDefinition)),
482
+ name: $,
483
+ })),
484
+ ],
485
+ })
486
+ } else {
487
+ // OOB/built-in step definition — not in XML export
488
+ const stepTypeSysId = record.get('step_type')?.asString()?.getValue()
489
+ if (!stepTypeSysId) {
490
+ logger.warn(`Step instance ${record.getId().getValue()} has no step_type reference`)
491
+ return { success: false }
492
+ }
493
+
494
+ const builtInDef = BUILT_IN_STEP_DEFINITIONS[stepTypeSysId]
495
+ if (!builtInDef) {
496
+ logger.warn(
497
+ `Unknown OOB step definition ${stepTypeSysId} for step instance ${record.getId().getValue()}`
498
+ )
499
+ return { success: false }
500
+ }
501
+
502
+ stepName = builtInDef.name
503
+
504
+ // Resolve variable values using the built-in definition's input/output mappings
505
+ for (const varValue of variableValues) {
506
+ const variableSysId = varValue.get('variable')?.asString()?.getValue()
507
+ const value = varValue.get('value')?.asString()?.getValue()
508
+ if (variableSysId) {
509
+ const entry = builtInDef.inputs[variableSysId] ?? builtInDef.outputs[variableSysId]
510
+ const elementName = entry ? getVarEntryName(entry) : undefined
511
+ const uiType = entry ? getVarEntryType(entry) : undefined
512
+ if (elementName && value != null) {
513
+ configObj[elementName] = uiType ? normalizeInputValue(value, uiType, record) : value
514
+ }
515
+ }
516
+ }
517
+
518
+ // Built-in/OOB step: emit actionStep.xxx reference
519
+ const stepIdentifier = getBuiltInStepIdentifier(stepTypeSysId)
520
+ stepDefinitionShape = stepIdentifier
521
+ ? new IdentifierShape({ source: record, name: stepIdentifier })
522
+ : builtInDef.name
523
+ }
524
+
525
+ // Build step instance config (2nd arg): $id, label, error_handling_type
526
+ const configProperties: globalThis.Record<string, unknown> = {
527
+ $id: NowIdShape.from(record),
528
+ }
529
+
530
+ const errorHandlingRaw = record.get('error_handling_type')
531
+ const errorHandlingType =
532
+ errorHandlingRaw?.ifNumber()?.getValue() ?? Number(errorHandlingRaw?.ifString()?.getValue())
533
+ if (errorHandlingType && errorHandlingType !== 1 && !Number.isNaN(errorHandlingType)) {
534
+ configProperties['error_handling_type'] = errorHandlingType
535
+ }
536
+
537
+ const label = record.get('label')?.asString()?.getValue()
538
+ if (label && label !== stepName) {
539
+ configProperties['label'] = label
540
+ }
541
+
542
+ const stepInstanceConfig = new ObjectShape({
543
+ source: record,
544
+ properties: configProperties,
545
+ })
546
+
547
+ // Build inputs (3rd arg): resolved variable values only
548
+ const inputsShape = new ObjectShape({
549
+ source: record,
550
+ properties: { ...configObj },
551
+ })
552
+
553
+ return {
554
+ success: true,
555
+ value: new CallExpressionShape({
556
+ source: record,
557
+ callee: 'wfa.actionStep',
558
+ args: [stepDefinitionShape, stepInstanceConfig, inputsShape],
559
+ }),
560
+ }
561
+ },
276
562
  },
277
563
  },
278
564
  })
@@ -280,6 +566,7 @@ export const StepInstancePlugin = Plugin.create({
280
566
  async function buildStepInstanceRecord({
281
567
  callExpression,
282
568
  factory,
569
+ diagnostics,
283
570
  values,
284
571
  sysId,
285
572
  stepDefinitionSysId,
@@ -288,12 +575,19 @@ async function buildStepInstanceRecord({
288
575
  }: {
289
576
  callExpression: StepInstanceShape
290
577
  factory: Factory
578
+ diagnostics: Diagnostics
291
579
  values: ObjectShape
292
580
  sysId: NowIdShape | Shape
293
581
  stepDefinitionSysId: string
294
582
  stepLabel: string
295
583
  inputs: ObjectShape | undefined
296
584
  }): Promise<BuildRecord> {
585
+ // resolve: false — keep PillShapes and other ResolvableShapes intact (same reason as createVariableRecords).
586
+ // Using .getValue() on the parent ObjectShape would deeply resolve nested PillShapes into symbols.
587
+ const valuesArray = values.properties({ resolve: false })
588
+ const cidMap = callExpression.getCidMap()
589
+ const pillTypeMap = callExpression.getPillTypeMap()
590
+
297
591
  // Create step instance record
298
592
  const stepInstanceRecord = await factory.createRecord({
299
593
  source: callExpression,
@@ -303,7 +597,8 @@ async function buildStepInstanceRecord({
303
597
  step_type: stepDefinitionSysId,
304
598
  order: 1,
305
599
  active: true,
306
- error_handling_type: ERROR_HANDLING_TYPE_MAP[values.get('errorHandlingType')?.getValue() as string] ?? 1,
600
+ error_handling_type:
601
+ ERROR_HANDLING_TYPE_MAP[valuesArray['errorHandlingType']?.ifString()?.getValue() ?? ''] ?? 1,
307
602
  label: stepLabel,
308
603
  },
309
604
  })
@@ -312,20 +607,31 @@ async function buildStepInstanceRecord({
312
607
  })
313
608
  const stepInstanceSysId = updatedStepInstanceRecord.getId().getValue()
314
609
  // Create sys_variable_value records for each input value
315
- const variableValueRecords = await createVariableRecords(values, inputs, factory, callExpression, stepInstanceSysId)
610
+ const stepDataRecords = await createVariableRecords(
611
+ valuesArray,
612
+ inputs,
613
+ factory,
614
+ callExpression,
615
+ stepInstanceSysId,
616
+ stepDefinitionSysId,
617
+ cidMap
618
+ )
316
619
  const extendedInputRecords: BuildRecord[] = []
317
620
  const extendedOutputRecords: BuildRecord[] = []
318
621
  const extendedVariableValueRecords: BuildRecord[] = []
622
+ const inputVariablesShape = valuesArray['inputVariables']
623
+ if (inputVariablesShape) {
624
+ const inputVarObj = inputVariablesShape.asObject()
625
+ const inputVarProps = inputVarObj.properties({ resolve: false })
626
+
627
+ const extInputTableName = `var__m_sys_hub_step_ext_input_${stepInstanceSysId}`
319
628
 
320
- const valuesArray = values.properties()
321
- // biome-ignore lint/suspicious/noExplicitAny: Dynamic input variable config
322
- const inputVariables = valuesArray['inputVariables']?.getValue() as Record<string, any>
323
- if (inputVariables) {
324
- for (const [name, config] of Object.entries(inputVariables)) {
325
- const label = config.label
326
- const mandatory = config.mandatory
327
- const defaultValue = config.defaultValue ?? ''
328
- const value = config.value
629
+ for (const [name, configShapeRaw] of Object.entries(inputVarProps)) {
630
+ const configObj = configShapeRaw.asObject()
631
+ const configProps = configObj.properties({ resolve: false })
632
+ const label = (configProps['label']?.ifString?.()?.getValue() ?? name) as string
633
+ const defaultValue = (configProps['defaultValue']?.ifString?.()?.getValue() ?? '') as string
634
+ const valueShape = configProps['value']
329
635
  const extInputRecord = await factory.createRecord({
330
636
  source: callExpression,
331
637
  table: 'sys_hub_step_ext_input',
@@ -334,89 +640,397 @@ async function buildStepInstanceRecord({
334
640
  model: stepInstanceSysId,
335
641
  model_id: stepInstanceSysId,
336
642
  model_table: 'sys_hub_step_instance',
337
- name: `var__m_sys_hub_step_ext_input_${stepInstanceSysId}`,
643
+ name: extInputTableName,
338
644
  element: name,
339
645
  label: label,
340
- mandatory: mandatory,
341
646
  default_value: defaultValue,
647
+ attributes: buildExtVarAttributes('string'),
648
+ internal_type: 'string',
342
649
  },
343
650
  })
344
651
  extendedInputRecords.push(extInputRecord)
345
- extendedVariableValueRecords.push(
346
- await factory.createRecord({
347
- source: callExpression,
348
- table: 'sys_variable_value',
349
- properties: {
350
- document: 'sys_hub_step_instance',
351
- document_key: stepInstanceSysId,
352
- order: 100,
353
- value: value,
354
- variable: extInputRecord.getId().getValue(),
355
- },
356
- })
357
- )
652
+
653
+ if (!valueShape) {
654
+ // No value provided — still create a sys_variable_value record with empty value
655
+ // so the platform recognizes the ext input variable exists
656
+ extendedVariableValueRecords.push(
657
+ await factory.createRecord({
658
+ source: callExpression,
659
+ table: 'sys_variable_value',
660
+ properties: {
661
+ document: 'sys_hub_step_instance',
662
+ document_key: stepInstanceSysId,
663
+ order: 100,
664
+ value: '',
665
+ variable: extInputRecord.getId().getValue(),
666
+ },
667
+ })
668
+ )
669
+ } else {
670
+ // Check if value is a datapill — if so, create sys_element_mapping instead of sys_variable_value
671
+ const pillString = resolveFieldToPillString(valueShape as Shape, cidMap)
672
+ if (pillString) {
673
+ collectPillTypes(pillString, pillTypeMap)
674
+ extendedVariableValueRecords.push(
675
+ await factory.createRecord({
676
+ source: callExpression,
677
+ table: 'sys_element_mapping',
678
+ properties: {
679
+ field: name,
680
+ id: stepInstanceSysId,
681
+ table: extInputTableName,
682
+ value: stripPillType(pillString),
683
+ },
684
+ })
685
+ )
686
+ } else if (valueShape.is(TemplateExpressionShape)) {
687
+ const { result, hasPills } = resolveTemplateExpression(
688
+ valueShape as TemplateExpressionShape,
689
+ cidMap,
690
+ pillTypeMap
691
+ )
692
+ if (hasPills) {
693
+ extendedVariableValueRecords.push(
694
+ await factory.createRecord({
695
+ source: callExpression,
696
+ table: 'sys_element_mapping',
697
+ properties: {
698
+ field: name,
699
+ id: stepInstanceSysId,
700
+ table: extInputTableName,
701
+ value: result,
702
+ },
703
+ })
704
+ )
705
+ } else {
706
+ extendedVariableValueRecords.push(
707
+ await factory.createRecord({
708
+ source: callExpression,
709
+ table: 'sys_variable_value',
710
+ properties: {
711
+ document: 'sys_hub_step_instance',
712
+ document_key: stepInstanceSysId,
713
+ order: 100,
714
+ value: result,
715
+ variable: extInputRecord.getId().getValue(),
716
+ },
717
+ })
718
+ )
719
+ }
720
+ } else {
721
+ // Simple value → store in sys_variable_value
722
+ const rawVal = valueShape.ifString()?.getValue() ?? valueShape.getValue?.()
723
+ const resolvedValue = typeof rawVal === 'symbol' ? '' : (rawVal ?? '')
724
+ extendedVariableValueRecords.push(
725
+ await factory.createRecord({
726
+ source: callExpression,
727
+ table: 'sys_variable_value',
728
+ properties: {
729
+ document: 'sys_hub_step_instance',
730
+ document_key: stepInstanceSysId,
731
+ order: 100,
732
+ value: resolvedValue,
733
+ variable: extInputRecord.getId().getValue(),
734
+ },
735
+ })
736
+ )
737
+ }
738
+ }
358
739
  }
359
740
  }
360
741
 
361
742
  const outputVariables = valuesArray['outputVariables']
743
+ const complexObjectRecords: BuildRecord[] = []
362
744
  if (outputVariables) {
363
745
  const outputItemShape = outputVariables.asObject()
364
- const outputProperties = outputItemShape.properties()
746
+ const outputProperties = outputItemShape.properties({ resolve: false })
365
747
 
366
748
  for (const [columnName, columnCallExpr] of Object.entries(outputProperties)) {
367
749
  const callExpr = columnCallExpr.as(CallExpressionShape)
368
- const columnType = callExpr.getCallee() // e.g., 'StringColumn', 'IntegerColumn'
750
+ const columnType = callExpr.getCallee() // e.g., 'StringColumn', 'IntegerColumn', 'FlowObject', 'FlowArray'
369
751
  const columnConfig = callExpr.getArgument(0)?.asObject()
370
752
  const columnLabel = columnConfig?.get('label')?.getValue() as string
371
753
  const mandatory = columnConfig?.get('mandatory')?.getValue() as boolean
372
- const internalType = COLUMN_API_TO_TYPE[columnType] || 'string'
373
- const extOutputRecord = await factory.createRecord({
374
- source: callExpression,
375
- table: 'sys_hub_step_ext_output',
376
- properties: {
377
- active: true,
378
- model: stepInstanceSysId,
379
- model_id: stepInstanceSysId,
380
- model_table: 'sys_hub_step_instance',
381
- name: `var__m_sys_hub_step_ext_output_${stepInstanceSysId}`,
382
- element: columnName,
383
- label: columnLabel || columnName,
384
- internal_type: internalType,
385
- mandatory: mandatory,
386
- },
387
- })
388
- extendedOutputRecords.push(extOutputRecord)
754
+
755
+ // Handle FlowObject and FlowArray complex objects
756
+ if (columnType === FLOW_OBJECT_API_NAME || columnType === FLOW_ARRAY_API_NAME) {
757
+ const coTypeName = columnConfig?.get('co_type_name')?.ifString()?.getValue()
758
+ const varSysId = `${stepInstanceSysId}_${columnName}`
759
+ const complexObjectId = coTypeName || `FD${varSysId}`
760
+ const isArray = columnType === FLOW_ARRAY_API_NAME
761
+
762
+ // Generate schema for complex object
763
+ const schemaObj = generateSchemaFromObject(
764
+ columnConfig,
765
+ complexObjectId,
766
+ columnName,
767
+ stepInstanceSysId,
768
+ 0,
769
+ isArray,
770
+ diagnostics,
771
+ false
772
+ )
773
+
774
+ // Create sys_complex_object record
775
+ const explicitId = columnConfig?.get('$id')?.ifString()?.getValue()
776
+ const complexRecord = await factory.createRecord({
777
+ source: callExpression,
778
+ table: 'sys_complex_object',
779
+ properties: {
780
+ name: complexObjectId,
781
+ namespace: 'FlowDesigner',
782
+ type: isArray ? 'complex_object_collection' : 'complex_object_schema',
783
+ serialized_content: JSON.stringify(schemaObj),
784
+ ...(explicitId ? { sys_id: explicitId } : {}),
785
+ },
786
+ })
787
+ complexObjectRecords.push(complexRecord)
788
+
789
+ // Get complex object attributes
790
+ const systemAttributes = getComplexObjectAttributes(schemaObj, complexObjectId, isArray)
791
+ const attributesStr = formatComplexObjectAttributes(systemAttributes)
792
+
793
+ // Create ext_output record with complex object type
794
+ const extOutputRecord = await factory.createRecord({
795
+ source: callExpression,
796
+ table: 'sys_hub_step_ext_output',
797
+ properties: {
798
+ active: true,
799
+ model: stepInstanceSysId,
800
+ model_id: stepInstanceSysId,
801
+ model_table: 'sys_hub_step_instance',
802
+ name: `var__m_sys_hub_step_ext_output_${stepInstanceSysId}`,
803
+ element: columnName,
804
+ label: columnLabel || columnName,
805
+ internal_type: 'string',
806
+ max_length: 65000,
807
+ mandatory: mandatory,
808
+ attributes: attributesStr,
809
+ },
810
+ })
811
+ extendedOutputRecords.push(extOutputRecord)
812
+ } else {
813
+ // Handle regular column types (StringColumn, IntegerColumn, etc.)
814
+ const internalType = COLUMN_API_TO_TYPE[columnType] || 'string'
815
+ const maxLength = columnConfig?.get('maxLength')?.ifNumber()?.getValue()
816
+ const hint = columnConfig?.get('hint')?.ifString()?.getValue()
817
+ const defaultValue = columnConfig?.get('default')?.ifString()?.getValue()
818
+ const extOutputRecord = await factory.createRecord({
819
+ source: callExpression,
820
+ table: 'sys_hub_step_ext_output',
821
+ properties: {
822
+ active: true,
823
+ model: stepInstanceSysId,
824
+ model_id: stepInstanceSysId,
825
+ model_table: 'sys_hub_step_instance',
826
+ name: `var__m_sys_hub_step_ext_output_${stepInstanceSysId}`,
827
+ element: columnName,
828
+ label: columnLabel || columnName,
829
+ internal_type: internalType,
830
+ mandatory: mandatory,
831
+ ...(maxLength != null ? { max_length: maxLength } : {}),
832
+ ...(hint ? { hint } : {}),
833
+ ...(defaultValue ? { default_value: defaultValue } : {}),
834
+ attributes: buildExtVarAttributes(internalType),
835
+ },
836
+ })
837
+ extendedOutputRecords.push(extOutputRecord)
838
+ }
389
839
  }
390
840
  }
391
841
 
392
842
  return updatedStepInstanceRecord.with(
393
- ...variableValueRecords,
843
+ ...stepDataRecords,
394
844
  ...extendedInputRecords,
395
845
  ...extendedVariableValueRecords,
396
- ...extendedOutputRecords
846
+ ...extendedOutputRecords,
847
+ ...complexObjectRecords
397
848
  )
398
849
  }
399
850
  async function createVariableRecords(
400
- values: ObjectShape,
851
+ valuesProperties: globalThis.Record<string, Shape>,
401
852
  inputs: ObjectShape | undefined,
402
853
  factory: Factory,
403
854
  callExpression: StepInstanceShape,
404
- stepInstanceSysId: string
855
+ stepInstanceSysId: string,
856
+ stepDefinitionSysId: string,
857
+ cidMap?: Map<string, string>
405
858
  ) {
406
- const properties = values.properties()
407
859
  // Filter out inputVariables, outputVariables and errorHandlingType as they are handled separately
408
- const filteredEntries = Object.entries(properties).filter(
860
+ const filteredEntries = Object.entries(valuesProperties).filter(
409
861
  ([key]) => key !== 'inputVariables' && key !== 'outputVariables' && key !== 'errorHandlingType'
410
862
  )
411
- return await Promise.all(
412
- filteredEntries.map(([key, valueShape]) => {
863
+
864
+ // Build reverse lookup (elementName → varSysId) from BUILT_IN_STEP_DEFINITIONS
865
+ const builtInDef = BUILT_IN_STEP_DEFINITIONS[stepDefinitionSysId]
866
+ const elementToVarSysId: globalThis.Record<string, string> = {}
867
+ if (builtInDef) {
868
+ for (const [varSysId, entry] of Object.entries(builtInDef.inputs)) {
869
+ elementToVarSysId[getVarEntryName(entry)] = varSysId
870
+ }
871
+ for (const [varSysId, entry] of Object.entries(builtInDef.outputs)) {
872
+ elementToVarSysId[getVarEntryName(entry)] = varSysId
873
+ }
874
+ }
875
+
876
+ const pillTypeMap = callExpression.getPillTypeMap()
877
+ const allRecords: BuildRecord[] = []
878
+ await Promise.all(
879
+ filteredEntries.map(async ([key, valueShape]) => {
880
+ // Try resolving from inputDefinitions shape first, then fall back to built-in map
881
+ let variableSysId = ''
413
882
  const input = inputs?.get(key)
414
- const variableSysId =
415
- input instanceof CallExpressionShape
416
- ? (input.getArgument(0).asObject().get('sysId')?.getValue() as string)
417
- : ''
418
- const actualValue = valueShape.getValue?.() ?? valueShape.toString()
419
- return factory.createRecord({
883
+ if (input instanceof CallExpressionShape) {
884
+ variableSysId = (input.getArgument(0).asObject().get('sysId')?.getValue() as string) ?? ''
885
+ }
886
+ if (!variableSysId) {
887
+ variableSysId = elementToVarSysId[key] ?? ''
888
+ }
889
+
890
+ // ApprovalDueDateShape: store JSON in sys_element_mapping.value (ServiceNow reads from there)
891
+ // and create sys_variable_value with empty value
892
+ if (valueShape.is(ApprovalDueDateShape)) {
893
+ const jsonValue = (valueShape as ApprovalDueDateShape).toString().getValue()
894
+ const [elementMappingRecord, variableValueRecord] = await Promise.all([
895
+ factory.createRecord({
896
+ source: callExpression,
897
+ table: 'sys_element_mapping',
898
+ properties: {
899
+ field: key,
900
+ id: stepInstanceSysId,
901
+ table: `var__m_sys_flow_step_definition_input_${stepDefinitionSysId}`,
902
+ value: jsonValue,
903
+ },
904
+ }),
905
+ factory.createRecord({
906
+ source: callExpression,
907
+ table: 'sys_variable_value',
908
+ properties: {
909
+ document: 'sys_hub_step_instance',
910
+ document_key: stepInstanceSysId,
911
+ order: 100,
912
+ // value intentionally empty: ServiceNow reads due_date JSON from sys_element_mapping, not here
913
+ value: '',
914
+ variable: variableSysId,
915
+ },
916
+ }),
917
+ ])
918
+ allRecords.push(elementMappingRecord, variableValueRecord)
919
+ return
920
+ }
921
+
922
+ // Handle TemplateValueShape specially - serialize to ServiceNow format.
923
+ // When TemplateValue contains datapills, the platform stores the entire encoded string
924
+ // (with pills inline) in a single sys_element_mapping record with field=<parent input name>.
925
+ let actualValue: unknown
926
+ if (valueShape.is(TemplateValueShape)) {
927
+ const templateObj = (valueShape as TemplateValueShape).getTemplateValue()
928
+ const entries: string[] = []
929
+ let hasPills = false
930
+
931
+ for (const [field, fieldShape] of templateObj.entries({ resolve: false })) {
932
+ const pillString = resolveFieldToPillString(fieldShape, cidMap)
933
+ if (pillString) {
934
+ collectPillTypes(pillString, pillTypeMap)
935
+ entries.push(`${field}=${stripPillType(pillString)}`)
936
+ hasPills = true
937
+ } else if (fieldShape.is(TemplateExpressionShape)) {
938
+ const resolved = resolveTemplateExpression(
939
+ fieldShape as TemplateExpressionShape,
940
+ cidMap,
941
+ pillTypeMap
942
+ )
943
+ if (resolved.hasPills) {
944
+ hasPills = true
945
+ }
946
+ const escapedValue = resolved.result.replace(/\^/g, '^^')
947
+ entries.push(`${field}=${escapedValue}`)
948
+ } else {
949
+ const resolved = fieldShape.getValue()
950
+ const escapedValue = String(resolved).replace(/\^/g, '^^')
951
+ entries.push(`${field}=${escapedValue}`)
952
+ }
953
+ }
954
+
955
+ const encodedValue = entries.join('^')
956
+
957
+ if (hasPills) {
958
+ // TemplateValue with datapills → single sys_element_mapping record
959
+ // No sys_variable_value for this field — platform reads from element_mapping only
960
+ allRecords.push(
961
+ await factory.createRecord({
962
+ source: callExpression,
963
+ table: 'sys_element_mapping',
964
+ properties: {
965
+ field: key,
966
+ id: stepInstanceSysId,
967
+ table: `var__m_sys_flow_step_definition_input_${stepDefinitionSysId}`,
968
+ value: encodedValue,
969
+ },
970
+ })
971
+ )
972
+ return
973
+ }
974
+
975
+ // No datapills — standard encoded string in sys_variable_value
976
+ actualValue = encodedValue ? `${encodedValue}^EQ` : ''
977
+ } else if (valueShape.is(DurationShape)) {
978
+ // DurationShape.getValue() returns a symbol, so use toString() instead
979
+ actualValue = (valueShape as DurationShape).toString().getValue()
980
+ } else if (valueShape.is(ApprovalRulesShape)) {
981
+ // ApprovalRulesShape.getValue() returns a JSON object, not a string.
982
+ // Previously fell through to the generic getValue() path which stored a raw [object Object].
983
+ // toString() correctly serializes it to the JSON string ServiceNow expects.
984
+ actualValue = valueShape.toString().getValue()
985
+ } else if (valueShape.is(TemplateExpressionShape)) {
986
+ const { result, hasPills } = resolveTemplateExpression(
987
+ valueShape as TemplateExpressionShape,
988
+ cidMap,
989
+ pillTypeMap
990
+ )
991
+ if (hasPills) {
992
+ allRecords.push(
993
+ await factory.createRecord({
994
+ source: callExpression,
995
+ table: 'sys_element_mapping',
996
+ properties: {
997
+ field: key,
998
+ id: stepInstanceSysId,
999
+ table: `var__m_sys_flow_step_definition_input_${stepDefinitionSysId}`,
1000
+ value: result,
1001
+ },
1002
+ })
1003
+ )
1004
+ return
1005
+ }
1006
+ actualValue = result
1007
+ } else {
1008
+ // Check if it's a wfa.dataPill() CallExpressionShape (step output reference)
1009
+ // that needs to be resolved to a pill string for sys_element_mapping
1010
+ const pillString = resolveFieldToPillString(valueShape as Shape, cidMap)
1011
+ if (pillString) {
1012
+ collectPillTypes(pillString, pillTypeMap)
1013
+ allRecords.push(
1014
+ await factory.createRecord({
1015
+ source: callExpression,
1016
+ table: 'sys_element_mapping',
1017
+ properties: {
1018
+ field: key,
1019
+ id: stepInstanceSysId,
1020
+ table: `var__m_sys_flow_step_definition_input_${stepDefinitionSysId}`,
1021
+ value: stripPillType(pillString),
1022
+ },
1023
+ })
1024
+ )
1025
+ return
1026
+ }
1027
+ const rawValue = valueShape.getValue?.()
1028
+ // Guard against symbol values (e.g., DurationShape, unresolved shapes with { resolve: false })
1029
+ // since factory.createRecord cannot serialize symbols. Fall back to empty string.
1030
+ actualValue = typeof rawValue === 'symbol' ? '' : (rawValue ?? '')
1031
+ }
1032
+
1033
+ const record = await factory.createRecord({
420
1034
  source: callExpression,
421
1035
  table: 'sys_variable_value',
422
1036
  properties: {
@@ -427,6 +1041,8 @@ async function createVariableRecords(
427
1041
  variable: variableSysId,
428
1042
  },
429
1043
  })
1044
+ allRecords.push(record)
430
1045
  })
431
1046
  )
1047
+ return allRecords
432
1048
  }