import { CallExpressionShape, Plugin, isSNScope } from '@servicenow/sdk-build-core' import { NowIdShape } from './now-id-plugin' const DL_DEFINITION = 'dl_definition' const DL_DEFINITION_REL_MATCH = 'dl_definition_rel_match' const DL_DEFINITION_REL_SET = 'dl_definition_rel_set' export const DataLookupPlugin = Plugin.create({ name: 'DataLookupPlugin', records: { [DL_DEFINITION]: { relationships: { [DL_DEFINITION_REL_MATCH]: { via: 'dl_definition', descendant: true, }, [DL_DEFINITION_REL_SET]: { via: 'dl_definition', descendant: true, }, }, async toShape(record, { descendants }) { const matchRecords = descendants .query(DL_DEFINITION_REL_MATCH) .filter((r) => r.get('dl_definition').equals(record.getId())) const setRecords = descendants .query(DL_DEFINITION_REL_SET) .filter((r) => r.get('dl_definition').equals(record.getId())) const matchRules = matchRecords.map((r) => r.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(r)), sourceField: $.from('source_table_field'), matcherField: $.from('matcher_table_field'), exactMatch: $.from('exact_match').toBoolean().def(false), })) ) const setRules = setRecords.map((r) => r.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(r)), targetField: $.from('source_table_field'), matcherField: $.from('matcher_table_field'), alwaysReplace: $.from('always_replace').toBoolean().def(false), })) ) return { success: true, value: new CallExpressionShape({ source: record, callee: 'DataLookup', args: [ record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $, sourceTable: $.from('source_table'), matcherTable: $.from('matcher_table'), active: $.from('active').toBoolean().def(true), runOnInsert: $.from('run_on_insert').toBoolean().def(true), runOnUpdate: $.from('run_on_update').toBoolean().def(false), runOnFormChange: $.from('run_on_form_change').toBoolean().def(true), matchRules: $.val(matchRules.length ? matchRules : undefined), setRules: $.val(setRules.length ? setRules : undefined), protectionPolicy: $.from('sys_policy').def(''), })), ], }), } }, }, [DL_DEFINITION_REL_MATCH]: { // Child records are pulled by the parent dl_definition toShape via descendants.query(), // so they must not be transformed independently. toShape() { return { success: false } }, }, [DL_DEFINITION_REL_SET]: { // Same as dl_definition_rel_match — rendered inline by the parent's toShape. toShape() { return { success: false } }, }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { factory, diagnostics, config }) { if (callExpression.getCallee() !== 'DataLookup') { return { success: false } } const arg = callExpression.getArgument(0).asObject() const name = arg.get('name').ifString()?.getValue() if (name && name.length > 40) { diagnostics.error( arg.get('name'), `Data Lookup Definition name must be 40 characters or fewer (got ${name.length}).` ) } const sourceTable = arg.get('sourceTable').ifString()?.getValue() ?? '' const matcherTable = arg.get('matcherTable').ifString()?.getValue() ?? '' const scope = config.scope if (sourceTable && !sourceTable.startsWith(`${scope}_`) && !isSNScope(scope) && scope !== 'global') { diagnostics.error( arg.get('sourceTable'), `sourceTable '${sourceTable}' must be in the same scope as the data lookup definition. Expected a table starting with '${scope}_'.` ) } if (matcherTable && !matcherTable.startsWith(`${scope}_`) && !isSNScope(scope) && scope !== 'global') { diagnostics.error( arg.get('matcherTable'), `matcherTable '${matcherTable}' must be in the same scope as the data lookup definition. Expected a table starting with '${scope}_'.` ) } const mainRecord = await factory.createRecord({ source: callExpression, table: DL_DEFINITION, explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ name: $, source_table: $.from('sourceTable'), matcher_table: $.from('matcherTable'), active: $.from('active').def(true), run_on_insert: $.from('runOnInsert').def(true), run_on_update: $.from('runOnUpdate').def(false), run_on_form_change: $.from('runOnFormChange').def(true), sys_policy: $.from('protectionPolicy'), })), }) const matchElements = arg.get('matchRules').ifArray()?.getElements() ?? [] const matchRecords = await Promise.all( matchElements.map(async (el) => { const rule = el.asObject() return factory.createRecord({ source: el, table: DL_DEFINITION_REL_MATCH, explicitId: rule.get('$id'), properties: rule.transform(({ $ }) => ({ dl_definition: $.val(mainRecord.getId()), source_table_field: $.from('sourceField'), matcher_table_field: $.from('matcherField'), exact_match: $.from('exactMatch').def(false), source_table: $.val(sourceTable), matcher_table: $.val(matcherTable), })), }) }) ) const setElements = arg.get('setRules').ifArray()?.getElements() ?? [] const setRecords = await Promise.all( setElements.map(async (el) => { const rule = el.asObject() return factory.createRecord({ source: el, table: DL_DEFINITION_REL_SET, explicitId: rule.get('$id'), properties: rule.transform(({ $ }) => ({ dl_definition: $.val(mainRecord.getId()), source_table_field: $.from('targetField'), matcher_table_field: $.from('matcherField'), always_replace: $.from('alwaysReplace').def(false), source_table: $.val(sourceTable), matcher_table: $.val(matcherTable), })), }) }) ) return { success: true, value: mainRecord.with(...matchRecords, ...setRecords), } }, }, ], })