import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeType, INodeTypeDescription, } from 'n8n-workflow'; import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import { FOUNDRY_BINDING_PATH, FOUNDRY_BASE_URL_ENV, FOUNDRY_CATALOG_PATH, FOUNDRY_DEFAULT_BASE_URL, NDC_FOUNDRY_BINDING_CREDENTIAL, NDC_NODE_ICON, } from '../shared/constants'; import { buildFoundryBindingPayload } from '../shared/contracts'; import { safeHttpError, safeInputError } from '../shared/errors'; import { loadDataProductOptions, ndcRequest, serviceBaseUrl } from '../shared/http'; export class NdcFoundryBinding implements INodeType { description: INodeTypeDescription = { displayName: 'NDC Foundry Binding', name: 'ndcFoundryBinding', icon: NDC_NODE_ICON, group: ['output'], version: 1, subtitle: '={{$parameter["dataProductId"]}}', description: 'Bind an approved Data Product to a Foundry page slot through the control plane', defaults: { name: 'NDC Foundry Binding' }, inputs: [NodeConnectionTypes.Main], outputs: [NodeConnectionTypes.Main], credentials: [{ name: NDC_FOUNDRY_BINDING_CREDENTIAL, required: true }], properties: [ { displayName: 'Uses the dedicated Foundry control-plane endpoint and a separate scoped workload grant. It fails closed if either is unavailable and never sends runtime facts or provider credentials to Foundry.', name: 'availabilityNotice', type: 'notice', default: '', }, { displayName: 'Application ID', name: 'applicationId', type: 'string', default: '', required: true, }, { displayName: 'Page ID', name: 'pageId', type: 'string', default: '', required: true, }, { displayName: 'Binding ID', name: 'bindingId', type: 'string', default: '', required: true, }, { displayName: 'Data Product Name or ID', name: 'dataProductId', type: 'options', typeOptions: { loadOptionsMethod: 'getDataProducts' }, default: '', required: true, }, { displayName: 'Slot ID', name: 'slotId', type: 'string', default: 'points', required: true, }, { displayName: 'Semantic Types', name: 'semanticTypes', type: 'string', typeOptions: { multipleValues: true, multipleValueButtonText: 'Add Semantic Type' }, default: [], required: true, }, { displayName: 'Field Projection', name: 'fieldProjection', type: 'string', typeOptions: { multipleValues: true, multipleValueButtonText: 'Add Field' }, default: [], }, ], }; methods = { loadOptions: { async getDataProducts(this: ILoadOptionsFunctions) { return loadDataProductOptions( this, NDC_FOUNDRY_BINDING_CREDENTIAL, FOUNDRY_BASE_URL_ENV, FOUNDRY_DEFAULT_BASE_URL, FOUNDRY_CATALOG_PATH, ); }, }, }; async execute(this: IExecuteFunctions): Promise { let body; try { body = buildFoundryBindingPayload({ applicationId: this.getNodeParameter('applicationId', 0) as string, pageId: this.getNodeParameter('pageId', 0) as string, binding: { id: this.getNodeParameter('bindingId', 0) as string, dataProductId: this.getNodeParameter('dataProductId', 0) as string, slotId: this.getNodeParameter('slotId', 0) as string, semanticTypes: this.getNodeParameter('semanticTypes', 0, []) as string[], fieldProjection: this.getNodeParameter('fieldProjection', 0, []) as string[], }, }); } catch (error) { throw new NodeOperationError(this.getNode(), safeInputError(error, 'ndc_foundry_binding_input_invalid')); } try { const baseUrl = serviceBaseUrl(FOUNDRY_BASE_URL_ENV, FOUNDRY_DEFAULT_BASE_URL); const response = await ndcRequest(this, NDC_FOUNDRY_BINDING_CREDENTIAL, { method: 'POST', url: `${baseUrl}${FOUNDRY_BINDING_PATH}`, body, }); return [[{ json: response as INodeExecutionData['json'], pairedItem: { item: 0 } }]]; } catch (error) { throw new NodeOperationError(this.getNode(), safeHttpError(error, 'ndc_foundry_binding_unavailable')); } } }