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