@servicenow/sdk-build-plugins 4.8.0 → 4.8.1
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.
- package/dist/flow/flow-logic/flow-logic-diagnostics.js +2 -1
- package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
- package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-action-definition-plugin.js +81 -16
- package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-definition-plugin.js +70 -7
- package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-instance-plugin.d.ts +35 -1
- package/dist/flow/plugins/flow-instance-plugin.js +240 -6
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
- package/dist/flow/plugins/step-instance-plugin.js +60 -0
- package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
- package/dist/flow/post-install.d.ts +2 -1
- package/dist/flow/post-install.js +31 -4
- package/dist/flow/post-install.js.map +1 -1
- package/dist/flow/utils/complex-object-resolver.js +4 -2
- package/dist/flow/utils/complex-object-resolver.js.map +1 -1
- package/dist/flow/utils/datapill-transformer.d.ts +5 -72
- package/dist/flow/utils/datapill-transformer.js +199 -28
- package/dist/flow/utils/datapill-transformer.js.map +1 -1
- package/dist/flow/utils/flow-io-to-record.js +24 -15
- package/dist/flow/utils/flow-io-to-record.js.map +1 -1
- package/dist/flow/utils/flow-shapes.d.ts +7 -1
- package/dist/flow/utils/flow-shapes.js +19 -0
- package/dist/flow/utils/flow-shapes.js.map +1 -1
- package/dist/flow/utils/flow-variable-processor.d.ts +6 -6
- package/dist/flow/utils/flow-variable-processor.js +8 -8
- package/dist/flow/utils/flow-variable-processor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/flow/flow-logic/flow-logic-diagnostics.ts +2 -1
- package/src/flow/flow-logic/flow-logic-plugin.ts +0 -1
- package/src/flow/plugins/flow-action-definition-plugin.ts +92 -25
- package/src/flow/plugins/flow-definition-plugin.ts +114 -8
- package/src/flow/plugins/flow-instance-plugin.ts +262 -6
- package/src/flow/plugins/step-instance-plugin.ts +73 -1
- package/src/flow/post-install.ts +36 -5
- package/src/flow/utils/complex-object-resolver.ts +4 -2
- package/src/flow/utils/datapill-transformer.ts +248 -36
- package/src/flow/utils/flow-io-to-record.ts +28 -14
- package/src/flow/utils/flow-shapes.ts +19 -0
- package/src/flow/utils/flow-variable-processor.ts +21 -10
- package/src/index.ts +1 -1
|
@@ -82,7 +82,12 @@ import { isDataPill } from '../utils/complex-object-resolver'
|
|
|
82
82
|
import { wrapWithDataPillCall, extractDataPillNames } from '../utils/pill-shape-helpers'
|
|
83
83
|
import { ArrayShape } from '@servicenow/sdk-build-core'
|
|
84
84
|
import { processFlowInputs, processFlowOutputs, processFlowVariables } from '../utils/flow-variable-processor'
|
|
85
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
buildUuidToIdentifierMap,
|
|
87
|
+
detectHoistedOutputBindings,
|
|
88
|
+
processInstanceForDatapills,
|
|
89
|
+
uuidToRecordMap,
|
|
90
|
+
} from '../utils/datapill-transformer'
|
|
86
91
|
import {
|
|
87
92
|
DEFAULT_PARAM_NAME,
|
|
88
93
|
PARALLEL_ORDER_SEPARATOR,
|
|
@@ -646,7 +651,6 @@ function extractAllPills(
|
|
|
646
651
|
})
|
|
647
652
|
}
|
|
648
653
|
} else {
|
|
649
|
-
// NOT a forEach parameter - preserve full property path
|
|
650
654
|
expression = new PropertyAccessShape({
|
|
651
655
|
source,
|
|
652
656
|
elements: [identifier, property],
|
|
@@ -774,6 +778,7 @@ function transformDataPill(
|
|
|
774
778
|
// {{flowVariables}} and {{outputs}} should remain as bare PropertyAccessShape without wrapping
|
|
775
779
|
if (stringValue === '{{flowVariables}}' || stringValue === '{{outputs}}') {
|
|
776
780
|
const allPills = extractAllPills(stringValue, uuidToIdentifierMap, source, diagnostics, parameterName)
|
|
781
|
+
|
|
777
782
|
if (allPills.length === 1 && allPills[0]?.expression.is(PropertyAccessShape)) {
|
|
778
783
|
return allPills[0].expression as PropertyAccessShape
|
|
779
784
|
}
|
|
@@ -1497,7 +1502,7 @@ function postProcessAssignSubflowOutputs(
|
|
|
1497
1502
|
subflowDefinitionRecord: Record,
|
|
1498
1503
|
logger: Logger
|
|
1499
1504
|
): ValuesJSON {
|
|
1500
|
-
if (!valuesJSON
|
|
1505
|
+
if (!valuesJSON?.outputsToAssign || !Array.isArray(valuesJSON.outputsToAssign)) {
|
|
1501
1506
|
return valuesJSON
|
|
1502
1507
|
}
|
|
1503
1508
|
|
|
@@ -1682,6 +1687,8 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1682
1687
|
return deleteMultipleDiff(existing, incoming, descendants, context)
|
|
1683
1688
|
},
|
|
1684
1689
|
async toShape(record, { descendants, diagnostics, transform, database, logger }) {
|
|
1690
|
+
const masterSnapshotId = record.get('master_snapshot')?.ifString()?.getValue()
|
|
1691
|
+
|
|
1685
1692
|
let type = record.get('type')?.getValue()
|
|
1686
1693
|
if (!type) {
|
|
1687
1694
|
logger.warn(`sys_hub_flow record has no type specified, defaulting to 'flow'`)
|
|
@@ -1771,6 +1778,15 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1771
1778
|
// Use recordToShapeMap to access ui_id from records (datapills reference ui_id, not $id)
|
|
1772
1779
|
const uuidToIdentifierMap = buildUuidToIdentifierMap(recordToShapeMap)
|
|
1773
1780
|
|
|
1781
|
+
// Hoisting guard: if any action nested inside a DoInParallel or TryCatch block has its output
|
|
1782
|
+
// referenced from outside that block, fall back to Record() API.
|
|
1783
|
+
if (detectHoistedOutputBindings(allInstances)) {
|
|
1784
|
+
logger.warn(
|
|
1785
|
+
`Hoisting detected: action instance outputs are referenced outside their DoInParallel/TryCatch scope, falling back to Record() api. Flow = ${record.getId().getValue()}`
|
|
1786
|
+
)
|
|
1787
|
+
return { success: false }
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1774
1790
|
// For the flow body, only use top-level instances (those without parent_ui_id). Also exclude Catch
|
|
1775
1791
|
// records — they are subsumed into their paired Try's `wfa.flowLogic.tryCatch(...)` shape via
|
|
1776
1792
|
// `connected_to`, and emitting them separately would produce a duplicate top-level statement.
|
|
@@ -1830,7 +1846,6 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1830
1846
|
// 2. Semantic pills: {{trigger.property}}, {{flow_variable.name}} → references to flow parameters (e.g., {{trigger.table_name}} → params.trigger.table_name)
|
|
1831
1847
|
|
|
1832
1848
|
// Process all instances (Action, Subflow, and Flow Logic) recursively
|
|
1833
|
-
// This handles datapill transformation for all instance types and their nested children
|
|
1834
1849
|
instanceShapes.forEach((instanceShape: Shape, index) => {
|
|
1835
1850
|
const result = processInstanceForDatapills(
|
|
1836
1851
|
instanceShape,
|
|
@@ -1848,6 +1863,7 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1848
1863
|
instanceShapes[index] = result.shape
|
|
1849
1864
|
}
|
|
1850
1865
|
})
|
|
1866
|
+
|
|
1851
1867
|
const flowBody = new ArrowFunctionShape({
|
|
1852
1868
|
source: record,
|
|
1853
1869
|
parameters: [new IdentifierShape({ source: record, name: paramName })],
|
|
@@ -1862,6 +1878,7 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1862
1878
|
record.transform(({ $ }) => ({
|
|
1863
1879
|
$id: $.val(NowIdShape.from(record)),
|
|
1864
1880
|
name: $,
|
|
1881
|
+
internalName: $.from('internal_name'),
|
|
1865
1882
|
description: $.def(EMPTY_STRING),
|
|
1866
1883
|
protectionPolicy: $.from('sys_policy').def(EMPTY_STRING),
|
|
1867
1884
|
runAs: $.from('run_as').def(RUN_AS_DEFAULT),
|
|
@@ -1877,6 +1894,7 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1877
1894
|
flowPriority: $.from('flow_priority')
|
|
1878
1895
|
.map((p) => p.ifString()?.getValue() || FLOW_PRIORITY_DEFAULT)
|
|
1879
1896
|
.def(FLOW_PRIORITY_DEFAULT),
|
|
1897
|
+
...(masterSnapshotId ? { masterSnapshot: $.val(masterSnapshotId) } : {}),
|
|
1880
1898
|
...(isSubflow
|
|
1881
1899
|
? {
|
|
1882
1900
|
annotation: $.def(EMPTY_STRING),
|
|
@@ -1975,12 +1993,14 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
1975
1993
|
}
|
|
1976
1994
|
|
|
1977
1995
|
const roles = flowConfiguration.get('runWithRoles').ifArray()?.getElements() ?? []
|
|
1996
|
+
const internalName = flowConfiguration.get('internalName').ifString()?.getValue()
|
|
1978
1997
|
const flowDefinitionRecord = await factory.createRecord({
|
|
1979
1998
|
source: callExpression,
|
|
1980
1999
|
table: 'sys_hub_flow',
|
|
1981
2000
|
explicitId: flowConfiguration.get('$id'),
|
|
1982
2001
|
properties: flowConfiguration.transform(({ $ }) => ({
|
|
1983
2002
|
name: $,
|
|
2003
|
+
...(internalName ? { internal_name: $.val(internalName) } : {}),
|
|
1984
2004
|
annotation: $.def(EMPTY_STRING),
|
|
1985
2005
|
description: $.def(EMPTY_STRING),
|
|
1986
2006
|
access: $.def(ACCESS_DEFAULT),
|
|
@@ -2046,9 +2066,11 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
2046
2066
|
relatedRecords.push(...flowVarRecords)
|
|
2047
2067
|
}
|
|
2048
2068
|
|
|
2069
|
+
const inputsConfig = isSubflow ? flowConfiguration.get('inputs').ifObject()?.asObject() : undefined
|
|
2070
|
+
const outputsConfig = isSubflow ? flowConfiguration.get('outputs').ifObject()?.asObject() : undefined
|
|
2071
|
+
|
|
2049
2072
|
if (isSubflow) {
|
|
2050
2073
|
// Process subflow inputs and outputs
|
|
2051
|
-
const inputsConfig = flowConfiguration.get('inputs').ifObject()?.asObject()
|
|
2052
2074
|
const inputRecords = await processFlowInputs(
|
|
2053
2075
|
inputsConfig,
|
|
2054
2076
|
flowDefinitionRecord,
|
|
@@ -2057,7 +2079,6 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
2057
2079
|
)
|
|
2058
2080
|
relatedRecords.push(...inputRecords)
|
|
2059
2081
|
|
|
2060
|
-
const outputsConfig = flowConfiguration.get('outputs').ifObject()?.asObject()
|
|
2061
2082
|
const outputRecords = await processFlowOutputs(
|
|
2062
2083
|
outputsConfig,
|
|
2063
2084
|
flowDefinitionRecord,
|
|
@@ -2260,16 +2281,101 @@ export const FlowDefinitionPlugin = Plugin.create({
|
|
|
2260
2281
|
)
|
|
2261
2282
|
|
|
2262
2283
|
const labelCacheData = Array.from(labelCacheMap.values())
|
|
2284
|
+
const labelCacheValue = labelCacheData.length > 0 ? JSON.stringify(labelCacheData) : '[]'
|
|
2285
|
+
|
|
2263
2286
|
const flowDefWithMetadata =
|
|
2264
2287
|
labelCacheData.length > 0
|
|
2265
2288
|
? flowDefinitionRecord.merge({
|
|
2266
|
-
label_cache:
|
|
2289
|
+
label_cache: labelCacheValue,
|
|
2267
2290
|
})
|
|
2268
2291
|
: flowDefinitionRecord
|
|
2269
2292
|
|
|
2293
|
+
const masterSnapshotFromConfig = flowConfiguration.get('masterSnapshot')?.ifString()?.getValue()
|
|
2294
|
+
|
|
2295
|
+
if (!masterSnapshotFromConfig) {
|
|
2296
|
+
return {
|
|
2297
|
+
success: true,
|
|
2298
|
+
value: flowDefWithMetadata.with(...relatedRecords),
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
const snapshotRecord = await factory.createRecord({
|
|
2303
|
+
source: callExpression,
|
|
2304
|
+
table: 'sys_hub_flow_snapshot',
|
|
2305
|
+
explicitId: masterSnapshotFromConfig,
|
|
2306
|
+
properties: flowConfiguration.transform(({ $ }) => ({
|
|
2307
|
+
access: $.def(ACCESS_DEFAULT),
|
|
2308
|
+
active: $.val(false),
|
|
2309
|
+
allow_high_security_roles: $.def(BOOLEAN_FALSE_STRING),
|
|
2310
|
+
annotation: $.def(EMPTY_STRING),
|
|
2311
|
+
attributes: $.def(EMPTY_STRING),
|
|
2312
|
+
callable_by_client_api: $.def(BOOLEAN_FALSE_STRING),
|
|
2313
|
+
category: $.def(EMPTY_STRING),
|
|
2314
|
+
description: $.def(EMPTY_STRING),
|
|
2315
|
+
flow_priority: $.from('flowPriority').def(FLOW_PRIORITY_DEFAULT),
|
|
2316
|
+
label_cache: $.val(labelCacheValue),
|
|
2317
|
+
master: $.val(true),
|
|
2318
|
+
name: $,
|
|
2319
|
+
natlang: $.def(EMPTY_STRING),
|
|
2320
|
+
outputs: $.def(EMPTY_STRING),
|
|
2321
|
+
parent_flow: $.val(flowDefinitionRecord.getId().getValue()),
|
|
2322
|
+
remote_trigger_id: $.def(EMPTY_STRING),
|
|
2323
|
+
run_as: $.from('runAs').def(RUN_AS_DEFAULT),
|
|
2324
|
+
run_with_roles: $.val(
|
|
2325
|
+
Array.from(
|
|
2326
|
+
new Set(roles.map((r) => r.ifRecord()?.getId().getValue() ?? r.getValue()))
|
|
2327
|
+
).join(',')
|
|
2328
|
+
),
|
|
2329
|
+
sc_callable: $.def(BOOLEAN_FALSE_STRING),
|
|
2330
|
+
status: $.val(FLOW_STATUS_DRAFT),
|
|
2331
|
+
sys_policy: $.from('protectionPolicy').def(EMPTY_STRING),
|
|
2332
|
+
type: isFlow ? $.def(FLOW_TYPE_FLOW) : $.def(FLOW_TYPE_SUBFLOW),
|
|
2333
|
+
version: $.def(FLOW_VERSION),
|
|
2334
|
+
})),
|
|
2335
|
+
})
|
|
2336
|
+
|
|
2337
|
+
const snapshotRelatedRecords: Record[] = []
|
|
2338
|
+
|
|
2339
|
+
if (isSubflow) {
|
|
2340
|
+
const snapshotInputRecords = await processFlowInputs(
|
|
2341
|
+
inputsConfig,
|
|
2342
|
+
snapshotRecord,
|
|
2343
|
+
factory,
|
|
2344
|
+
diagnostics,
|
|
2345
|
+
'sys_hub_flow_snapshot'
|
|
2346
|
+
)
|
|
2347
|
+
snapshotRelatedRecords.push(...snapshotInputRecords)
|
|
2348
|
+
|
|
2349
|
+
const snapshotOutputRecords = await processFlowOutputs(
|
|
2350
|
+
outputsConfig,
|
|
2351
|
+
snapshotRecord,
|
|
2352
|
+
factory,
|
|
2353
|
+
diagnostics,
|
|
2354
|
+
'sys_hub_flow_snapshot'
|
|
2355
|
+
)
|
|
2356
|
+
snapshotRelatedRecords.push(...snapshotOutputRecords)
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
if (flowConfiguration.has('flowVariables')) {
|
|
2360
|
+
const snapshotVarRecords = await processFlowVariables(
|
|
2361
|
+
flowConfiguration.get('flowVariables').asObject(),
|
|
2362
|
+
snapshotRecord,
|
|
2363
|
+
factory,
|
|
2364
|
+
diagnostics,
|
|
2365
|
+
'sys_hub_flow_snapshot'
|
|
2366
|
+
)
|
|
2367
|
+
snapshotRelatedRecords.push(...snapshotVarRecords)
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
const snapshotId = snapshotRecord.getId().getValue()
|
|
2371
|
+
const finalFlowRecord = flowDefWithMetadata.merge({
|
|
2372
|
+
latest_snapshot: snapshotId,
|
|
2373
|
+
master_snapshot: snapshotId,
|
|
2374
|
+
})
|
|
2375
|
+
|
|
2270
2376
|
return {
|
|
2271
2377
|
success: true,
|
|
2272
|
-
value:
|
|
2378
|
+
value: finalFlowRecord.with(...relatedRecords, snapshotRecord, ...snapshotRelatedRecords),
|
|
2273
2379
|
}
|
|
2274
2380
|
},
|
|
2275
2381
|
},
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
ActionSubflowInstanceShape,
|
|
46
46
|
ApprovalDueDateShape,
|
|
47
47
|
ApprovalRulesShape,
|
|
48
|
+
getRefSysId,
|
|
48
49
|
InlineScriptShape,
|
|
49
50
|
} from '../utils/flow-shapes'
|
|
50
51
|
import { FDInlineScriptCallShape } from './inline-script-plugin'
|
|
@@ -142,6 +143,119 @@ function parseComplexObjectValues(obj: unknown): unknown {
|
|
|
142
143
|
return parseValue(obj)
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Returns the complex-object payload type ('object' | 'array.object') when the given value is a
|
|
148
|
+
* serialized FD complex-object JSON string (has both `complexObjectSchema` and `complexObject`).
|
|
149
|
+
* Returns `undefined` for any other value.
|
|
150
|
+
*/
|
|
151
|
+
export function getComplexObjectPayloadType(value: unknown): 'object' | 'array.object' | undefined {
|
|
152
|
+
if (typeof value !== 'string') {
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(value)
|
|
157
|
+
if (parsed && typeof parsed === 'object' && 'complexObjectSchema' in parsed && 'complexObject' in parsed) {
|
|
158
|
+
const co = (parsed as { complexObject?: unknown }).complexObject
|
|
159
|
+
return co && typeof co === 'object' && '$COCollectionField' in co ? 'array.object' : 'object'
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// not JSON / not a complex-object payload
|
|
163
|
+
}
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Decodes a serialized FD complex-object payload (FlowObject or FlowArray) into a plain
|
|
169
|
+
* JavaScript object/array by extracting the `$cv.$v` leaf values from `complexObject`.
|
|
170
|
+
*
|
|
171
|
+
* Returns `undefined` if the string cannot be decoded (malformed, empty complexObject, etc.).
|
|
172
|
+
* Falls back gracefully so callers can keep the verbatim string for spoke/snapshot references.
|
|
173
|
+
*
|
|
174
|
+
* Exported only for unit testing.
|
|
175
|
+
*/
|
|
176
|
+
type PlainObj = { [key: string]: unknown }
|
|
177
|
+
|
|
178
|
+
export function decodeComplexObjectPayload(jsonString: string): PlainObj | PlainObj[] | unknown[] | undefined {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(jsonString)
|
|
181
|
+
if (!parsed || typeof parsed !== 'object' || !('complexObject' in parsed)) {
|
|
182
|
+
return undefined
|
|
183
|
+
}
|
|
184
|
+
return decodeComplexObjectNode(parsed.complexObject as PlainObj)
|
|
185
|
+
} catch {
|
|
186
|
+
return undefined
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function decodeComplexObjectNode(co: PlainObj): PlainObj | PlainObj[] | unknown[] {
|
|
191
|
+
if ('$COCollectionField' in co && Array.isArray(co['$COCollectionField'])) {
|
|
192
|
+
const arr = co['$COCollectionField'] as unknown[]
|
|
193
|
+
// Primitive arrays (e.g. FlowArray<StringColumn>, FlowArray<IntegerColumn>) store plain
|
|
194
|
+
// strings/numbers in $COCollectionField — not $cv-wrapped objects. Return them directly
|
|
195
|
+
// to avoid decodeComplexObjectItem treating string characters or number properties as fields.
|
|
196
|
+
if (arr.length === 0 || typeof arr[0] !== 'object' || arr[0] === null) {
|
|
197
|
+
return arr
|
|
198
|
+
}
|
|
199
|
+
return (arr as PlainObj[]).map(decodeComplexObjectItem)
|
|
200
|
+
}
|
|
201
|
+
return decodeComplexObjectItem(co)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function decodeComplexObjectItem(item: PlainObj): PlainObj {
|
|
205
|
+
const result: PlainObj = {}
|
|
206
|
+
for (const [key, val] of Object.entries(item)) {
|
|
207
|
+
if (key === 'name$' || key.includes('.$')) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(val)) {
|
|
211
|
+
// Nested FlowArray<primitive> (Array.String, Array.Integer, Array.Boolean, etc.)
|
|
212
|
+
// within a FlowObject: buildCv stores plain values directly as a bare array —
|
|
213
|
+
// no $COCollectionField wrapper for primitive element types.
|
|
214
|
+
// Must check Array.isArray BEFORE typeof object because arrays are objects in JS.
|
|
215
|
+
if (val.length === 0 || typeof val[0] !== 'object' || val[0] === null) {
|
|
216
|
+
result[key] = val
|
|
217
|
+
} else {
|
|
218
|
+
result[key] = (val as PlainObj[]).map(decodeComplexObjectItem)
|
|
219
|
+
}
|
|
220
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
221
|
+
const v = val as PlainObj
|
|
222
|
+
if ('$cv' in v) {
|
|
223
|
+
// ServiceNow UI format: leaf value wrapped in { $cv: { $c, $v } }
|
|
224
|
+
const cv = v['$cv'] as PlainObj
|
|
225
|
+
const rawVal = cv?.['$v'] ?? ''
|
|
226
|
+
const javaClass = cv?.['$c'] as string | undefined
|
|
227
|
+
// Only coerce to number for known numeric Java types — avoids silently converting
|
|
228
|
+
// string choice values (e.g. state: '1' stored as java.lang.String) to numbers.
|
|
229
|
+
const isNumericType =
|
|
230
|
+
javaClass === 'java.lang.Integer' ||
|
|
231
|
+
javaClass === 'java.lang.Long' ||
|
|
232
|
+
javaClass === 'java.lang.Double' ||
|
|
233
|
+
javaClass === 'java.lang.Float'
|
|
234
|
+
result[key] =
|
|
235
|
+
isNumericType && typeof rawVal === 'string' && rawVal.trim() !== '' ? Number(rawVal) : rawVal
|
|
236
|
+
} else if ('$COCollectionField' in v) {
|
|
237
|
+
// Nested FlowArray<object> (Array.Object) within a FlowObject:
|
|
238
|
+
// buildCv wraps complex array elements in { $COCollectionField: [...] }.
|
|
239
|
+
const arr = v['$COCollectionField'] as unknown[]
|
|
240
|
+
if (arr.length === 0 || typeof arr[0] !== 'object' || arr[0] === null) {
|
|
241
|
+
result[key] = arr
|
|
242
|
+
} else {
|
|
243
|
+
result[key] = (arr as PlainObj[]).map(decodeComplexObjectItem)
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Nested FlowObject
|
|
247
|
+
result[key] = decodeComplexObjectItem(v)
|
|
248
|
+
}
|
|
249
|
+
} else if (val !== undefined) {
|
|
250
|
+
// Fluent build format: raw primitive stored directly (no $cv wrapping).
|
|
251
|
+
// buildArrayPayload skips buildCv for array elements, so string/number/boolean
|
|
252
|
+
// values appear as-is in $COCollectionField items.
|
|
253
|
+
result[key] = val
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result
|
|
257
|
+
}
|
|
258
|
+
|
|
145
259
|
/**
|
|
146
260
|
* Normalize raw input value based on uiType.
|
|
147
261
|
* - Attempts to JSON.parse strings.
|
|
@@ -244,7 +358,13 @@ function buildInstanceToShape({
|
|
|
244
358
|
|
|
245
359
|
const definitionInputs =
|
|
246
360
|
instanceDef instanceof Record
|
|
247
|
-
? instanceDef
|
|
361
|
+
? instanceDef
|
|
362
|
+
.flat()
|
|
363
|
+
.filter(
|
|
364
|
+
(v) =>
|
|
365
|
+
v.getTable() === inputsTableName &&
|
|
366
|
+
getRefSysId(v.get('model')) === instanceDef.getId().getValue()
|
|
367
|
+
)
|
|
248
368
|
: undefined
|
|
249
369
|
|
|
250
370
|
const zippedInputs = record.get(zippedColumn)?.ifString()?.getValue() ?? ''
|
|
@@ -586,6 +706,36 @@ function buildInlineScriptShapeFromXml(
|
|
|
586
706
|
return undefined
|
|
587
707
|
}
|
|
588
708
|
|
|
709
|
+
/**
|
|
710
|
+
* Reconstructs a TemplateValueShape with FDInlineScriptCallShape sub-fields when the
|
|
711
|
+
* V2 JSON payload carries scripted sub-fields (scriptActive=false at top level, with
|
|
712
|
+
* a `script` map keyed by sub-field name).
|
|
713
|
+
*
|
|
714
|
+
* Called during XML → Fluent transform to restore wfa.inlineScript() calls that were
|
|
715
|
+
* inside TemplateValue({...}) in the original Fluent source.
|
|
716
|
+
*/
|
|
717
|
+
export function applyTemplateValueSubFieldScripts(
|
|
718
|
+
value: unknown,
|
|
719
|
+
script: { [key: string]: { scriptActive: boolean; script: string } } | undefined,
|
|
720
|
+
source: Record
|
|
721
|
+
): unknown {
|
|
722
|
+
if (!script || !(value instanceof TemplateValueShape)) {
|
|
723
|
+
return value
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const templateValue = value.getTemplateValue()
|
|
727
|
+
const newProps: globalThis.Record<string, unknown> = {}
|
|
728
|
+
for (const [field, fieldShape] of templateValue.entries({ resolve: false })) {
|
|
729
|
+
const fieldScript = script[field]
|
|
730
|
+
if (fieldScript?.scriptActive && fieldScript?.script) {
|
|
731
|
+
newProps[field] = new FDInlineScriptCallShape({ source, scriptContent: fieldScript.script })
|
|
732
|
+
} else {
|
|
733
|
+
newProps[field] = fieldShape
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return new TemplateValueShape({ source, value: newProps })
|
|
737
|
+
}
|
|
738
|
+
|
|
589
739
|
/**
|
|
590
740
|
* Build an ObjectShape representing instance inputs from a base64-zipped JSON payload.
|
|
591
741
|
* This is used by both subflow and action instances.
|
|
@@ -689,9 +839,29 @@ function buildInputsShapeFromZipped({
|
|
|
689
839
|
)
|
|
690
840
|
}
|
|
691
841
|
}
|
|
692
|
-
// Use the specialized shape or fallback to normalized value
|
|
693
|
-
|
|
694
|
-
|
|
842
|
+
// Use the specialized shape or fallback to normalized value.
|
|
843
|
+
// Complex-object (FlowObject/FlowArray) inputs: decode the $cv payload to a
|
|
844
|
+
// plain object so typed action references (Path 1) satisfy TypeScript types
|
|
845
|
+
// and the build's resolveComplexInput path re-encodes them correctly.
|
|
846
|
+
// If decode fails (empty payload / spoke references with no data), fall back
|
|
847
|
+
// to the verbatim string so Path 2 (snapshot/string reference) still works.
|
|
848
|
+
// applyTemplateValueSubFieldScripts reconstructs FDInlineScriptCallShape
|
|
849
|
+
// sub-fields for TemplateValue inputs that carry a `script` map.
|
|
850
|
+
let baseShape: unknown
|
|
851
|
+
if (inputShape) {
|
|
852
|
+
baseShape = inputShape
|
|
853
|
+
} else if (getComplexObjectPayloadType(value) !== undefined) {
|
|
854
|
+
// Only decode when the action schema is available (Path 1: typed action reference,
|
|
855
|
+
// definitionInputs is populated from sys_hub_action_input records).
|
|
856
|
+
// For Path 2 (snapshot/string reference, definitionInputs is undefined),
|
|
857
|
+
// keep the verbatim string so build can re-emit it byte-for-byte.
|
|
858
|
+
const decoded =
|
|
859
|
+
definitionInputs !== undefined ? decodeComplexObjectPayload(value as string) : undefined
|
|
860
|
+
baseShape = decoded !== undefined ? decoded : value
|
|
861
|
+
} else {
|
|
862
|
+
baseShape = normalizeInputValue(value as string, uiType ?? parameter.type, record, logger)
|
|
863
|
+
}
|
|
864
|
+
props[name] = applyTemplateValueSubFieldScripts(baseShape, script, record)
|
|
695
865
|
}
|
|
696
866
|
} catch (e) {
|
|
697
867
|
const recordInfo = `${record.getTable()}.${record.getId().getValue()}`
|
|
@@ -941,6 +1111,22 @@ function processInputValue(
|
|
|
941
1111
|
return primitiveValue
|
|
942
1112
|
}
|
|
943
1113
|
|
|
1114
|
+
// Preserve path: the input value is already a serialized FD complex-object payload
|
|
1115
|
+
// (FlowObject/FlowArray). Re-emit it verbatim with the correct parameter.type instead
|
|
1116
|
+
// of reconstructing it — works for typed, string/sys_id, and spoke references alike.
|
|
1117
|
+
const preservedComplexType = getComplexObjectPayloadType(primitiveValue)
|
|
1118
|
+
if (preservedComplexType !== undefined) {
|
|
1119
|
+
return {
|
|
1120
|
+
name: ELEMENT_MAPPING_FIELD_ALIASES_REVERSE[inputName] ?? inputName,
|
|
1121
|
+
value: primitiveValue,
|
|
1122
|
+
displayValue: displayValues?.has(inputName) ? displayValues.get(inputName) : primitiveValue,
|
|
1123
|
+
scriptActive: false,
|
|
1124
|
+
parameter: {
|
|
1125
|
+
type: preservedComplexType,
|
|
1126
|
+
},
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
944
1130
|
let finalValue: unknown = primitiveValue
|
|
945
1131
|
let finalType: unknown = options?.type ?? 'string'
|
|
946
1132
|
|
|
@@ -1091,7 +1277,13 @@ function validateMandatoryFields(
|
|
|
1091
1277
|
definitionName?: string
|
|
1092
1278
|
): Record[] | undefined {
|
|
1093
1279
|
const inputDefinitions = definition
|
|
1094
|
-
? definition
|
|
1280
|
+
? definition
|
|
1281
|
+
.flat()
|
|
1282
|
+
.filter(
|
|
1283
|
+
(record) =>
|
|
1284
|
+
record.getTable() === inputTable &&
|
|
1285
|
+
getRefSysId(record.get('model')) === definition.getId().getValue()
|
|
1286
|
+
)
|
|
1095
1287
|
: undefined
|
|
1096
1288
|
|
|
1097
1289
|
if (inputDefinitions) {
|
|
@@ -1367,6 +1559,9 @@ async function prepareActionInstanceValueJson(
|
|
|
1367
1559
|
properties: Object.fromEntries(mergedResults),
|
|
1368
1560
|
})
|
|
1369
1561
|
|
|
1562
|
+
// Collect scripted sub-fields for any TemplateValue inputs
|
|
1563
|
+
const tvScripts = extractTemplateValueScripts(instanceInputs)
|
|
1564
|
+
|
|
1370
1565
|
const result: unknown[] = []
|
|
1371
1566
|
|
|
1372
1567
|
// When isActionDefString is true, there's no action definition in fluent
|
|
@@ -1428,6 +1623,18 @@ async function prepareActionInstanceValueJson(
|
|
|
1428
1623
|
}
|
|
1429
1624
|
}
|
|
1430
1625
|
|
|
1626
|
+
// Attach scripted sub-fields for any TemplateValue inputs
|
|
1627
|
+
if (tvScripts.size > 0) {
|
|
1628
|
+
for (const item of result) {
|
|
1629
|
+
if (typeof item === 'object' && item !== null && 'name' in item) {
|
|
1630
|
+
const scripts = tvScripts.get((item as globalThis.Record<string, unknown>)['name'] as string)
|
|
1631
|
+
if (scripts) {
|
|
1632
|
+
;(item as globalThis.Record<string, unknown>)['script'] = scripts
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1431
1638
|
return result
|
|
1432
1639
|
}
|
|
1433
1640
|
|
|
@@ -1460,7 +1667,10 @@ async function prepareSubflowInstanceValueJson(
|
|
|
1460
1667
|
properties: Object.fromEntries(mergedResults),
|
|
1461
1668
|
})
|
|
1462
1669
|
|
|
1463
|
-
|
|
1670
|
+
// Collect scripted sub-fields for any TemplateValue inputs
|
|
1671
|
+
const tvScripts = extractTemplateValueScripts(instanceInputs)
|
|
1672
|
+
|
|
1673
|
+
const subflowResult = objShape
|
|
1464
1674
|
.keys()
|
|
1465
1675
|
.filter((key) => key !== 'waitForCompletion')
|
|
1466
1676
|
.map((key) => {
|
|
@@ -1497,6 +1707,20 @@ async function prepareSubflowInstanceValueJson(
|
|
|
1497
1707
|
|
|
1498
1708
|
return result
|
|
1499
1709
|
})
|
|
1710
|
+
|
|
1711
|
+
// Attach scripted sub-fields for any TemplateValue inputs
|
|
1712
|
+
if (tvScripts.size > 0) {
|
|
1713
|
+
for (const item of subflowResult) {
|
|
1714
|
+
if (typeof item === 'object' && item !== null && 'name' in item) {
|
|
1715
|
+
const scripts = tvScripts.get((item as globalThis.Record<string, unknown>)['name'] as string)
|
|
1716
|
+
if (scripts) {
|
|
1717
|
+
;(item as globalThis.Record<string, unknown>)['script'] = scripts
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
return subflowResult
|
|
1500
1724
|
}
|
|
1501
1725
|
|
|
1502
1726
|
/**
|
|
@@ -1560,6 +1784,8 @@ async function resolveObjectShapeRecursively(shape: ObjectShape, transform: Tran
|
|
|
1560
1784
|
}
|
|
1561
1785
|
|
|
1562
1786
|
resolvedObject[key] = resolvedArray
|
|
1787
|
+
} else if (valueShape instanceof FDInlineScriptCallShape) {
|
|
1788
|
+
resolvedObject[key] = 'fd-scripted'
|
|
1563
1789
|
} else {
|
|
1564
1790
|
// For all other shapes (primitives), get the value directly
|
|
1565
1791
|
resolvedObject[key] = valueShape.getValue()
|
|
@@ -1601,6 +1827,36 @@ function wrapSpecialShape(shape: Shape, resolvedValue: unknown, source: Source):
|
|
|
1601
1827
|
return resolvedValue
|
|
1602
1828
|
}
|
|
1603
1829
|
|
|
1830
|
+
/**
|
|
1831
|
+
* Extracts scripted sub-fields from any TemplateValue inputs.
|
|
1832
|
+
* Returns a map of input name → { field: { scriptActive, script } } for inputs where
|
|
1833
|
+
* at least one sub-field is an FDInlineScriptCallShape (wfa.inlineScript()).
|
|
1834
|
+
* This map is then attached as a `script` property on the corresponding result item so
|
|
1835
|
+
* ServiceNow can store the script content separately (sys_hub_input_scripts).
|
|
1836
|
+
*/
|
|
1837
|
+
function extractTemplateValueScripts(
|
|
1838
|
+
instanceInputs: ObjectShape
|
|
1839
|
+
): Map<string, globalThis.Record<string, { scriptActive: true; script: string }>> {
|
|
1840
|
+
const result = new Map()
|
|
1841
|
+
for (const [key, shape] of instanceInputs.entries({ resolve: false })) {
|
|
1842
|
+
if (!shape.is(TemplateValueShape)) {
|
|
1843
|
+
continue
|
|
1844
|
+
}
|
|
1845
|
+
const scriptedFields: globalThis.Record<string, { scriptActive: true; script: string }> = {}
|
|
1846
|
+
for (const [field, fieldShape] of (shape as TemplateValueShape)
|
|
1847
|
+
.getTemplateValue()
|
|
1848
|
+
.entries({ resolve: false })) {
|
|
1849
|
+
if (fieldShape instanceof FDInlineScriptCallShape) {
|
|
1850
|
+
scriptedFields[field] = { scriptActive: true, script: fieldShape.getScriptContent() }
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (Object.keys(scriptedFields).length > 0) {
|
|
1854
|
+
result.set(key, scriptedFields)
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return result
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1604
1860
|
/**
|
|
1605
1861
|
* Checks for datapills and resolves them in instance inputs.
|
|
1606
1862
|
* Similar to resolveObjectShapeRecursively but returns key-value pairs and handles special shapes.
|