import { CallExpressionShape, Plugin, type Diagnostics, type Shape, type ObjectShape } from '@servicenow/sdk-build-core' import { NowIdShape } from '../now-id-plugin' const SYS_ALIAS = 'sys_alias' // Maps developer-facing camelCase connection type labels to ServiceNow platform values const CONNECTION_TYPE_MAP: Record = { httpConnection: 'http_connection', jdbcConnection: 'jdbc_connection', basicConnection: 'sys_connection', jmsConnection: 'orch_jms_ds', } // Reverse map: platform values to camelCase labels (used in toShape) const CONNECTION_TYPE_REVERSE_MAP: Record = Object.fromEntries( Object.entries(CONNECTION_TYPE_MAP).map(([label, platform]) => [platform, label]) ) // Default retry policy sys_ids by platform connection type // Mirrors ServiceNow client scripts: "Default RetryPolicy onLoad new form" / "Default RetryPolicy onChange Conn-type" const DEFAULT_RETRY_POLICY: Record = { http_connection: 'ef751ff07301330025d71afe2ff6a7f9', // Default HTTP Retry Policy jdbc_connection: 'e8ddcb7573331010cbfec9d234f6a7fd', // Default JDBC Retry Policy sys_connection: 'e385484773100010e3a71afe2ff6a709', // Default Basic Connection Retry Policy } /** * Hints when `connectionType` is set on a credential-only alias. * * Based on sys_alias UI policies: * - "Show / hide connection type" — connectionType is only shown when type='connection' */ function hintConnectionTypeForCredential( connectionTypeShape: Shape, aliasType: string | undefined, diagnostics: Diagnostics ): void { if (aliasType === 'credential' && connectionTypeShape.isDefined()) { diagnostics.warn( connectionTypeShape, `'connectionType' is ignored when type is 'credential'. It only applies to 'connection' type aliases.` ) } } /** * Resolves a developer-facing camelCase connection type to the ServiceNow platform value. * Returns the platform default ('http_connection') if the value is not provided. */ function resolveConnectionType(connectionTypeShape: Shape): string { const value = connectionTypeShape.ifString()?.getValue() if (!value) { return 'http_connection' } return CONNECTION_TYPE_MAP[value] ?? 'http_connection' } /** * Computes the `id` field value for a sys_alias record. * ServiceNow's virtual field calculation: scope + '.' + name (global scope omits prefix). * Without this, the stored 'id' column is empty and Flow Designer's * idISNOTEMPTY filter hides the alias from dropdowns. */ function computeAliasId(name: string, scope: string): string { const sanitizedName = name.replace(/[^A-Za-z0-9_]/g, '_') return scope !== 'global' ? `${scope}.${sanitizedName}` : sanitizedName } /** * Validates fields related to child aliases. * * Based on sys_alias UI policy "HideEmptyParentField" — the parent field is hidden * in the UI when type='credential'. */ function validateChildAliasFields(arg: ObjectShape, diagnostics: Diagnostics): void { const parent = arg.get('parent') const type = arg.get('type') // Warn if parent is set for credential-only alias — UI hides the field so it has no effect. if (type?.isString() && type.getValue() === 'credential' && parent?.isDefined()) { diagnostics.warn( parent, `'parent' field is not visible in the UI when type is 'credential'. ` + `Parent aliases are only supported for 'connection' type aliases.` ) } } /** * Validates fields related to 'connection' type alias. * * Based on sys_alias UI policies: * - "Show/Hide Multiple connection flag" — multipleConnections is only shown when type='connection' * - "Show / hide Default Retry Policy field" — retryPolicy is only shown when type='connection' */ function validateConnectionOnlyFields(arg: ObjectShape, aliasType: string | undefined, diagnostics: Diagnostics): void { if (aliasType === 'credential') { const multipleConnections = arg.get('multipleConnections') if (multipleConnections?.isDefined()) { diagnostics.warn( multipleConnections, `'multipleConnections' is ignored when type is 'credential'. It only applies to 'connection' type aliases.` ) } const retryPolicy = arg.get('retryPolicy') if (retryPolicy?.isDefined()) { diagnostics.warn( retryPolicy, `'retryPolicy' is ignored when type is 'credential'. It only applies to 'connection' type aliases.` ) } } } export const AliasPlugin = Plugin.create({ name: 'AliasPlugin', records: { [SYS_ALIAS]: { toShape(record) { // Resolve connection_type and alias type to determine default retry policy const aliasType = record.get('type').toString().getValue() || 'connection' const connType = record.get('connection_type').toString().getValue() || 'http_connection' // For credential aliases, retry_policy is hidden in the UI and has no effect — // treat whatever value is present as the default so it gets suppressed in Fluent output. const retryPolicyValue = record.get('retry_policy').toString().getValue() const defaultRetryPolicy = aliasType === 'connection' ? (DEFAULT_RETRY_POLICY[connType] ?? '') : retryPolicyValue const camelCaseConnType = CONNECTION_TYPE_REVERSE_MAP[connType] ?? 'httpConnection' return { success: true, value: new CallExpressionShape({ source: record, callee: 'Alias', args: [ record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $, type: $.def('connection'), connectionType: $.from('connection_type').val(camelCaseConnType).def('httpConnection'), description: $.def(''), parent: $.def(''), configurationTemplate: $.from('configuration_template').def(''), retryPolicy: $.from('retry_policy').def(defaultRetryPolicy), multipleConnections: $.from('multiple_connections').toBoolean().def(false), protectionPolicy: $.from('sys_policy').def(''), })), ], }), } }, }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { factory, diagnostics, config }) { if (callExpression.getCallee() !== 'Alias') { return { success: false } } const argRaw = callExpression.getArgument(0) if (!argRaw?.isObject()) { return { success: false } } const arg = argRaw.asObject() // Validate required 'name' property. // TypeScript enforces `name: string`, but the empty-string check is semantic and // cannot be expressed in the type system. const name = arg.get('name') const nameValue = name.ifString()?.getValue().trim() ?? '' if (nameValue === '') { diagnostics.error(name.isDefined() ? name : arg, `'name' is required and cannot be empty.`) return { success: false } } // Resolve type and connectionType const typeShape = arg.get('type') const aliasType = typeShape.ifString()?.getValue() ?? 'connection' const connectionTypeShape = arg.get('connectionType') const resolvedConnectionType = resolveConnectionType(connectionTypeShape) // Cross-field hints hintConnectionTypeForCredential(connectionTypeShape, aliasType, diagnostics) validateChildAliasFields(arg, diagnostics) validateConnectionOnlyFields(arg, aliasType, diagnostics) // Resolve default retry policy (only for connection type) const defaultRetryPolicy = aliasType === 'connection' ? (DEFAULT_RETRY_POLICY[resolvedConnectionType] ?? '') : '' const aliasId = computeAliasId(nameValue, config.scope) // Create the sys_alias record const record = await factory.createRecord({ source: callExpression, table: SYS_ALIAS, explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ name: $, id: $.val(aliasId), type: $.def('connection'), connection_type: $.from('connectionType').val(resolvedConnectionType).def('http_connection'), description: $.def(''), parent: $, configuration_template: $.from('configurationTemplate'), retry_policy: $.from('retryPolicy').def(defaultRetryPolicy), multiple_connections: $.from('multipleConnections').def(false), sys_policy: $.from('protectionPolicy'), })), }) return { success: true, value: record } }, }, ], })