UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

950 lines 67.2 kB
"use strict"; /* eslint-disable no-div-regex */ /* eslint-disable line-comment-position */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SKLEngine = void 0; const openapi_operation_executor_1 = require("@comake/openapi-operation-executor"); const rmlmapper_js_1 = require("@comake/rmlmapper-js"); const axios_1 = __importDefault(require("axios")); const jsonpath_plus_1 = require("jsonpath-plus"); const rdf_validate_shacl_1 = __importDefault(require("rdf-validate-shacl")); const constants_1 = require("./constants"); const customCapabilities_1 = require("./customCapabilities"); const globalHooks_1 = require("./hooks/globalHooks"); const logger_1 = require("./logger"); const Mapper_1 = require("./mapping/Mapper"); const Exists_1 = require("./storage/operator/Exists"); const In_1 = require("./storage/operator/In"); const InversePath_1 = require("./storage/operator/InversePath"); const Not_1 = require("./storage/operator/Not"); const OneOrMorePath_1 = require("./storage/operator/OneOrMorePath"); const SequencePath_1 = require("./storage/operator/SequencePath"); const ZeroOrMorePath_1 = require("./storage/operator/ZeroOrMorePath"); const SparqlQueryAdapter_1 = require("./storage/query-adapter/sparql/SparqlQueryAdapter"); const Util_1 = require("./util/Util"); const Vocabularies_1 = require("./util/Vocabularies"); class SKLEngine { constructor(options) { this.queryAdapter = new SparqlQueryAdapter_1.SparqlQueryAdapter(options); this.disableValidation = options.disableValidation; this.globalCallbacks = options.callbacks; this.inputFiles = options.inputFiles; this.skdsEndpointUrl = options.endpointUrl; this.scriptPath = options.scriptPath; if (options.functions) { this.functions = Object.fromEntries(Object.entries(options.functions).map(([key, func]) => [ key, (data = {}) => { // Add the SKL instance to the data object // eslint-disable-next-line @typescript-eslint/no-this-alias data.skl = this; // Call the original function return func(data); } ])); } this.isDebugMode = options.debugMode ?? false; logger_1.Logger.getInstance(this.isDebugMode); // eslint-disable-next-line func-style const getCapabilityHandler = (getTarget, property) => async (capabilityArgs, capabilityConfig) => this.executeCapabilityByName(property, capabilityArgs, capabilityConfig); this.capability = new Proxy({}, { get: getCapabilityHandler }); } setCodeExecutor(codeExecutor) { this.codeExecutor = codeExecutor; } async executeRawQuery(query) { return await this.queryAdapter.executeRawQuery(query); } async executeRawUpdate(query) { return await this.queryAdapter.executeRawUpdate(query); } async executeRawConstructQuery(query, frame) { return await this.queryAdapter.executeRawConstructQuery(query, frame); } async find(options) { try { // Create context for hooks const context = { entities: [], operation: 'find', operationParameters: { options } }; // Execute before read hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.BEFORE, context); try { const entity = await this.queryAdapter.find(options); if (!entity) { throw new Error(`No schema found with fields matching ${JSON.stringify(options)}`); } // Execute after read hook with updated context const updatedContext = { ...context, entities: [entity] }; const afterHookResult = await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, entity); // The after hook can transform the result return afterHookResult || entity; } catch (error) { // Execute error read hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, context, error); throw error; } } catch (hookError) { // If hook execution itself fails, still propagate the error throw hookError; } } async findBy(where, notFoundErrorMessage) { const context = { entities: [], operation: 'findBy', operationParameters: { where } }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.BEFORE, context); try { const entity = await this.queryAdapter.findBy(where); if (entity) { const updatedContext = { ...context, entities: [entity] }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, entity); return entity; } throw new Error(notFoundErrorMessage ?? `No schema found with fields matching ${JSON.stringify(where)}`); } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, { entities: [], operation: 'findBy', operationParameters: { where } }, error); throw error; } } async findByIfExists(options) { try { const entity = await this.findBy(options); return entity; } catch { return undefined; } } async findAll(options) { const context = { entities: [], operation: 'findAll', operationParameters: { options } }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.BEFORE, context); try { const entities = await this.queryAdapter.findAll(options); const updatedContext = { ...context, entities }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, entities); return entities; } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, context, error); throw error; } } async groupBy(options) { const context = { entities: [], operation: 'groupBy', operationParameters: { options } }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.BEFORE, context); try { const result = await this.queryAdapter.groupBy(options); const updatedContext = { ...context, result }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, result); return result; } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, context, error); throw error; } } async findAllBy(where) { const context = { entities: [], operation: 'findAllBy', operationParameters: { where } }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.BEFORE, context); try { const entities = await this.queryAdapter.findAllBy(where); const updatedContext = { ...context, entities }; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, entities); return entities; } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, context, error); throw error; } } async exists(options) { return await this.queryAdapter.exists(options); } async count(options) { return await this.queryAdapter.count(options); } async save(entityOrEntities) { try { // Ensure we always work with an array of entities const entityArray = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; const isSingleEntity = !Array.isArray(entityOrEntities); // Execute before create hook await globalHooks_1.globalHooks.executeBeforeCreate(entityArray); try { // Validate entities await this.validateEntitiesConformToNounSchema(entityArray); // Save entities const savedEntities = await this.queryAdapter.save(entityArray); // Execute after create hook await globalHooks_1.globalHooks.executeAfterCreate(savedEntities); // Return single entity or array based on what was passed in return isSingleEntity ? savedEntities[0] : savedEntities; } catch (error) { // Execute error create hook await globalHooks_1.globalHooks.executeErrorCreate(entityArray, error); throw error; } } catch (hookError) { // If hook execution itself fails, still propagate the error throw hookError; } } async update(idOrIds, attributes) { try { // Ensure we always work with an array of IDs const idArray = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; const isSingleEntity = !Array.isArray(idOrIds); // Execute before update hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.BEFORE, { entities: [], operation: 'update', operationParameters: { idArray, attributes } }); try { // Validate and update if (idArray.length > 1) { await this.validateEntitiesWithIdsConformsToNounSchemaForAttributes(idArray, attributes); } else { await this.validateEntityWithIdConformsToNounSchemaForAttributes(idArray[0], attributes); } await this.queryAdapter.update(isSingleEntity ? idArray[0] : idArray, attributes); // Execute after update hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.AFTER, { entities: [], operation: 'update', operationParameters: { idArray, attributes } }); } catch (error) { // Execute error update hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.ERROR, { entities: [], operation: 'update', operationParameters: { idArray, attributes } }, error); throw error; } } catch (hookError) { // If hook execution itself fails, still propagate the error throw hookError; } } async validateEntitiesConformToNounSchema(entities) { const entitiesByType = this.groupEntitiesByType(entities); for (const type of Object.keys(entitiesByType)) { const noun = await this.findByIfExists({ id: type }); if (noun) { const parentNouns = await this.getSuperClassesOfNoun(type); for (const currentNoun of [noun, ...parentNouns]) { const entitiesOfType = entitiesByType[type]; const nounSchemaWithTarget = { ...currentNoun, [constants_1.SHACL.targetNode]: entitiesOfType.map((entity) => ({ [constants_1.PROP_ENTITY_ID]: entity[constants_1.PROP_ENTITY_ID] })) }; const report = await this.convertToQuadsAndValidateAgainstShape(entitiesOfType, nounSchemaWithTarget); if (!report.conforms) { const entityIds = entitiesOfType.map((entity) => entity[constants_1.PROP_ENTITY_ID]); this.throwValidationReportError(report, `Entity ${entityIds.join(', ')} does not conform to the ${currentNoun[constants_1.PROP_ENTITY_ID]} schema.`); } } } } } groupEntitiesByType(entities) { return entities.reduce((groupedEntities, entity) => { const entityTypes = Array.isArray(entity[constants_1.PROP_ENTITY_TYPE]) ? entity[constants_1.PROP_ENTITY_TYPE] : [entity[constants_1.PROP_ENTITY_TYPE]]; for (const type of entityTypes) { if (!groupedEntities[type]) { groupedEntities[type] = []; } groupedEntities[type].push(entity); } return groupedEntities; }, {}); } async getSuperClassesOfNoun(noun) { return await this.getParentsOfSelector(noun); } async getSuperClassesOfNouns(nouns) { return await this.getParentsOfSelector((0, In_1.In)(nouns)); } async getParentsOfSelector(selector) { return await this.findAll({ where: { id: (0, InversePath_1.InversePath)({ subPath: (0, OneOrMorePath_1.OneOrMorePath)({ subPath: constants_1.RDFS.subClassOf }), value: selector }) } }); } async validateEntityConformsToNounSchema(entity) { const nounIds = Array.isArray(entity[constants_1.PROP_ENTITY_TYPE]) ? entity[constants_1.PROP_ENTITY_TYPE] : [entity[constants_1.PROP_ENTITY_TYPE]]; const directNouns = await this.findAllBy({ id: (0, In_1.In)(nounIds) }); if (directNouns.length > 0) { const existingNounIds = directNouns.map((noun) => noun[constants_1.PROP_ENTITY_ID]); const parentNouns = await this.getSuperClassesOfNouns(existingNounIds); for (const currentNoun of [...directNouns, ...parentNouns]) { const nounSchemaWithTarget = { ...currentNoun, [constants_1.SHACL.targetNode]: { [constants_1.PROP_ENTITY_ID]: entity[constants_1.PROP_ENTITY_ID] } }; const report = await this.convertToQuadsAndValidateAgainstShape(entity, nounSchemaWithTarget); if (!report.conforms) { this.throwValidationReportError(report, `Entity ${entity[constants_1.PROP_ENTITY_ID]} does not conform to the ${currentNoun[constants_1.PROP_ENTITY_ID]} schema.`); } } } } async validateEntitiesWithIdsConformsToNounSchemaForAttributes(ids, attributes) { for (const id of ids) { await this.validateEntityWithIdConformsToNounSchemaForAttributes(id, attributes); } } async getNounsAndParentNounsOfEntity(id) { return await this.findAllBy({ id: (0, InversePath_1.InversePath)({ subPath: (0, SequencePath_1.SequencePath)({ subPath: [constants_1.RDF.type, (0, ZeroOrMorePath_1.ZeroOrMorePath)({ subPath: constants_1.RDFS.subClassOf })] }), value: id }) }); } async validateEntityWithIdConformsToNounSchemaForAttributes(id, attributes) { const nouns = await this.getNounsAndParentNounsOfEntity(id); for (const currentNoun of nouns) { if (constants_1.SHACL.property in currentNoun) { const nounProperties = (0, Util_1.ensureArray)(currentNoun[constants_1.SHACL.property]).filter((property) => { const path = property[constants_1.SHACL.path]; if (typeof path === 'string' && path in attributes) { return true; } if (typeof path === 'object' && constants_1.PROP_ENTITY_ID in path && path[constants_1.PROP_ENTITY_ID] in attributes) { return true; } return false; }); if (nounProperties.length > 0) { const nounSchemaWithTarget = { [constants_1.PROP_ENTITY_TYPE]: constants_1.SHACL.NodeShape, [constants_1.SHACL.targetNode]: { [constants_1.PROP_ENTITY_ID]: id }, [constants_1.SHACL.property]: nounProperties }; const attributesWithId = { ...attributes, [constants_1.PROP_ENTITY_ID]: id }; const report = await this.convertToQuadsAndValidateAgainstShape(attributesWithId, nounSchemaWithTarget); if (!report.conforms) { this.throwValidationReportError(report, `Entity ${id} does not conform to the ${currentNoun[constants_1.PROP_ENTITY_ID]} schema.`); } } } } } async delete(idOrIds) { try { // Ensure we always work with an array of IDs const idArray = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; // Execute before delete hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.BEFORE, { entities: [], operation: 'delete', operationParameters: { idArray } }); try { await this.queryAdapter.delete(idArray); // Execute after delete hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.AFTER, { entities: [], operation: 'delete', operationParameters: { idArray } }); } catch (error) { // Execute error delete hook await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.ERROR, { entities: [], operation: 'delete', operationParameters: { idArray } }, error); throw error; } } catch (hookError) { // If hook execution itself fails, still propagate the error throw hookError; } } async destroy(entityOrEntities) { if (Array.isArray(entityOrEntities)) { return await this.queryAdapter.destroy(entityOrEntities); } return await this.queryAdapter.destroy(entityOrEntities); } async destroyAll() { return await this.queryAdapter.destroyAll(); } async performMapping(args, mapping, frame, capabilityConfig, jsExecutionOptions) { if (!Array.isArray(mapping) && mapping[constants_1.PROP_ENTITY_TYPE] === constants_1.EngineConstants.spec.codeFunction && this.isJavaScriptCode((0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.codeBody]))) { return this.executeJavaScriptMapping(args, (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.codeBody]), jsExecutionOptions ?? {}); } const functions = { ...this.functions, ...capabilityConfig?.functions }; const mapper = new Mapper_1.Mapper({ functions }); return await mapper.apply(args, mapping, frame ?? {}); } async executeTrigger(integration, payload) { const triggerToCapabilityMapping = await this.findTriggerCapabilityMapping(integration); const capabilityArgs = await this.performParameterMappingOnArgsIfDefined(payload, triggerToCapabilityMapping); const capabilityId = await this.performCapabilityMappingWithArgs(payload, triggerToCapabilityMapping); if (capabilityId) { const mappedCapability = (await this.findBy({ id: capabilityId })); await this.executeCapability(mappedCapability, capabilityArgs); } } async findTriggerCapabilityMapping(integration) { const triggerCapabilityMappingNew = (await this.findBy({ type: constants_1.EngineConstants.spec.capabilityMapping, [constants_1.EngineConstants.prop.capability]: integration, [constants_1.EngineConstants.prop.capabilityType]: constants_1.EngineConstants.spec.triggerCapabilityMapping }, `Failed to find a Trigger Capability mapping for integration ${integration}`)); if (triggerCapabilityMappingNew) { return triggerCapabilityMappingNew; } throw new Error(`Failed to find a Trigger Capability mapping for integration ${integration}`); } async executeCapabilityByName(capabilityName, capabilityArgs, capabilityConfig) { const capability = await this.findCapabilityWithName(capabilityName); return await this.executeCapability(capability, capabilityArgs, capabilityConfig); } async findCapabilityWithName(capabilityName) { return (await this.findBy({ type: constants_1.EngineConstants.spec.capability, [constants_1.EngineConstants.prop.label]: capabilityName }, `Failed to find the capability ${capabilityName} in the schema.`)); } async executeCapability(capability, capabilityArgs, capabilityConfig) { this.globalCallbacks?.onCapabilityStart?.(capability[constants_1.PROP_ENTITY_ID], capabilityArgs); if (capabilityConfig?.callbacks?.onCapabilityStart) { logger_1.Logger.getInstance().log('Capability arguments', capabilityArgs); capabilityConfig.callbacks.onCapabilityStart(capability[constants_1.PROP_ENTITY_ID], capabilityArgs); } const { mapping, account } = await this.findMappingForCapabilityContextually(capability[constants_1.PROP_ENTITY_ID], capabilityArgs); logger_1.Logger.getInstance().log('Mapping', JSON.stringify(mapping)); const shouldValidate = this.shouldValidate(capabilityConfig); if (shouldValidate) { await this.assertCapabilityParamsMatchParameterSchemas(capabilityArgs, capability); } try { // Execute capability mapping before hook if appropriate - // works for any mapping that can be used as a verb mapping if (mapping) { await globalHooks_1.globalHooks.executeBeforeExecuteCapabilityMapping([capabilityArgs], mapping); } const verbReturnValue = await this.executeMapping(mapping, capabilityArgs, capabilityConfig, account); if (shouldValidate) { await this.assertCapabilityReturnValueMatchesReturnTypeSchema(verbReturnValue, capability); } // Execute capability mapping after hook if appropriate if (mapping) { await globalHooks_1.globalHooks.executeAfterExecuteCapabilityMapping([capabilityArgs], mapping, verbReturnValue); } this.globalCallbacks?.onCapabilityEnd?.(capability[constants_1.PROP_ENTITY_ID], verbReturnValue); if (capabilityConfig?.callbacks?.onCapabilityEnd) { capabilityConfig.callbacks.onCapabilityEnd(capability[constants_1.PROP_ENTITY_ID], verbReturnValue); } return verbReturnValue; } catch (error) { // Execute capability mapping error hook if appropriate if (mapping) { await globalHooks_1.globalHooks.executeErrorExecuteCapabilityMapping([capabilityArgs], mapping, error); } throw error; } } async findMappingForCapabilityContextually(capabilityId, args) { if (args.mapping) { const mapping = await this.findByIfExists({ id: args.mapping }); if (!mapping) { throw new Error(`Mapping ${args.mapping} not found.`); } return { mapping: mapping }; } if (args.noun) { const mapping = await this.findCapabilityNounMapping(capabilityId, args.noun); if (mapping) { return { mapping }; } } if (args.account) { const account = await this.findBy({ id: args.account }); const integratedProductId = account[constants_1.EngineConstants.prop.integration][constants_1.PROP_ENTITY_ID]; const mapping = await this.findCapabilityIntegrationMapping(capabilityId, integratedProductId); if (mapping) { return { mapping, account }; } } const mappings = await this.findAllBy({ type: constants_1.EngineConstants.spec.mapping, [constants_1.EngineConstants.prop.capability]: capabilityId, [constants_1.EngineConstants.prop.integration]: (0, Not_1.Not)((0, Exists_1.Exists)()), [constants_1.EngineConstants.prop.object]: (0, Not_1.Not)((0, Exists_1.Exists)()) }); if (mappings.length === 1) { return { mapping: mappings[0] }; } if (mappings.length > 1) { throw new Error('Multiple mappings found for capability, please specify one.'); } if (args.noun) { throw new Error(`Mapping between noun ${args.noun} and capability ${capabilityId} not found.`); } if (args.account) { throw new Error(`Mapping between account ${args.account} and capability ${capabilityId} not found.`); } throw new Error(`No mapping found.`); } async executeMapping(mapping, args, capabilityConfig, account) { args = await this.addPreProcessingMappingToArgs(mapping, args, capabilityConfig); let returnValue; if (constants_1.EngineConstants.prop.capabilityId in mapping || constants_1.EngineConstants.prop.capabilityMapping in mapping) { const capabilityId = await this.performCapabilityMappingWithArgs(args, mapping, capabilityConfig); const mappedArgs = await this.performParameterMappingOnArgsIfDefined({ ...args, capabilityId }, mapping, capabilityConfig); logger_1.Logger.getInstance().log('Mapped args', mappedArgs); returnValue = await this.executeCapabilityMapping(mapping, args, mappedArgs, capabilityConfig); } else { const mappedArgs = await this.performParameterMappingOnArgsIfDefined(args, mapping, capabilityConfig); logger_1.Logger.getInstance().log('Mapped args', mappedArgs); if (constants_1.EngineConstants.prop.operationId in mapping || constants_1.EngineConstants.prop.operationMapping in mapping) { returnValue = (await this.executeOperationMapping(mapping, mappedArgs, args, account, capabilityConfig)); } else if (constants_1.EngineConstants.prop.series in mapping) { returnValue = await this.executeSeriesMapping(mapping, mappedArgs, capabilityConfig); } else if (constants_1.EngineConstants.prop.parallel in mapping) { returnValue = await this.executeParallelMapping(mapping, mappedArgs, capabilityConfig); } else { returnValue = mappedArgs; } } return await this.performReturnValueMappingWithFrameIfDefined(returnValue, mapping, capabilityConfig); } shouldValidate(capabilityConfig) { return capabilityConfig?.disableValidation === undefined ? this.disableValidation !== true : !capabilityConfig.disableValidation; } async executeOperationMapping(mapping, mappedArgs, originalArgs, account, capabilityConfig) { const operationInfo = await this.performOperationMappingWithArgs(originalArgs, mapping, capabilityConfig); const response = await this.performOperation(operationInfo, mappedArgs, originalArgs, account, capabilityConfig); logger_1.Logger.getInstance().log('Original response', JSON.stringify(response)); return response; } async executeSeriesMapping(mapping, args, capabilityConfig) { const seriesCapabilityMappingsList = this.rdfListToArray(mapping[constants_1.EngineConstants.prop.series]); const seriesCapabilityArgs = { originalCapabilityParameters: args, previousCapabilityReturnValue: {}, allStepsResults: [] }; return await this.executeSeriesFromList(seriesCapabilityMappingsList, seriesCapabilityArgs, capabilityConfig); } rdfListToArray(list) { if (!(constants_1.RML_LIST in list)) { return [ list[constants_1.RDF.first], ...(0, rmlmapper_js_1.getIdFromNodeObjectIfDefined)(list[constants_1.RDF.rest]) === constants_1.RDF.nil ? [] : this.rdfListToArray(list[constants_1.RDF.rest]) ]; } return list[constants_1.RML_LIST]; } async executeSeriesFromList(list, args, capabilityConfig) { const nextCapabilityMapping = list[0]; const returnValue = await this.executeMapping(nextCapabilityMapping, args, capabilityConfig); if (list.length > 1) { return await this.executeSeriesFromList(list.slice(1), { ...args, previousCapabilityReturnValue: returnValue, allStepsResults: [...args.allStepsResults ?? [], returnValue] }, capabilityConfig); } return returnValue; } async executeCapabilityMapping(capabilityMapping, originalArgs, mappedArgs, capabilityConfig) { const capabilityId = await this.performCapabilityMappingWithArgs(originalArgs, capabilityMapping, capabilityConfig); if (capabilityId) { if (capabilityId === constants_1.EngineConstants.dataSource.update) { await this.updateEntityFromcapabilityArgs(mappedArgs); return {}; } if (capabilityId === constants_1.EngineConstants.dataSource.save) { return await this.saveEntityOrEntitiesFromcapabilityArgs(mappedArgs); } if (capabilityId === constants_1.EngineConstants.dataSource.destroy) { return await this.destroyEntityOrEntitiesFromcapabilityArgs(mappedArgs); } if (capabilityId === constants_1.EngineConstants.dataSource.findAll) { return await this.findAll(mappedArgs); } if (capabilityId === constants_1.EngineConstants.dataSource.find) { return await this.find(mappedArgs); } if (capabilityId === constants_1.EngineConstants.dataSource.count) { return await this.countAndWrapValueFromcapabilityArgs(mappedArgs); } if (capabilityId === constants_1.EngineConstants.dataSource.exists) { return await this.existsAndWrapValueFromcapabilityArgs(mappedArgs); } // Check for custom capabilities if (customCapabilities_1.globalCustomCapabilities.has(capabilityId)) { return await customCapabilities_1.globalCustomCapabilities.execute(capabilityId, mappedArgs, this, capabilityConfig); } return await this.findAndExecuteCapability(capabilityId, mappedArgs, capabilityConfig); } return {}; } async addPreProcessingMappingToArgs(capabilityMapping, args, capabilityConfig) { if (constants_1.EngineConstants.prop.preProcessingMapping in capabilityMapping) { const preMappingArgs = await this.performMapping(args, capabilityMapping[constants_1.EngineConstants.prop.preProcessingMapping], (0, Util_1.getValueIfDefined)(capabilityMapping[constants_1.EngineConstants.prop.preProcessingMappingFrame]), capabilityConfig); return { ...args, preProcessedParameters: preMappingArgs }; } return args; } replaceTypeAndId(entity) { if (typeof entity !== 'object') { throw new Error('Entity is not an object'); } const clonedEntity = structuredClone(entity); if (clonedEntity[constants_1.EngineConstants.prop.type]) { clonedEntity[constants_1.PROP_ENTITY_TYPE] = clonedEntity[constants_1.EngineConstants.prop.type]; } if (clonedEntity[constants_1.EngineConstants.prop.identifier]) { clonedEntity[constants_1.PROP_ENTITY_ID] = Vocabularies_1.SKL_DATA_NAMESPACE + clonedEntity[constants_1.EngineConstants.prop.identifier]; } return clonedEntity; } async updateEntityFromcapabilityArgs(args) { let ids = args.id ?? args.ids; if (!Array.isArray(ids)) { ids = [ids]; } // FIX: Temporary fix for the issue where the id always getting prefixed with the namespace ids = ids.map((id) => (id.startsWith('http') ? id : `${Vocabularies_1.SKL_DATA_NAMESPACE}${id}`)); await this.update(ids, args.attributes); } async saveEntityOrEntitiesFromcapabilityArgs(args) { if (args.entity && typeof args.entity === 'object') { args.entity = this.replaceTypeAndId(args.entity); } if (args.entities && Array.isArray(args.entities)) { args.entities = args.entities.map(this.replaceTypeAndId); } return await this.save(args.entity ?? args.entities); } async destroyEntityOrEntitiesFromcapabilityArgs(args) { if (args.entity && typeof args.entity === 'object') { args.entity = this.replaceTypeAndId(args.entity); } if (args.entities && Array.isArray(args.entities)) { args.entities = args.entities.map(this.replaceTypeAndId); } return await this.destroy(args.entity ?? args.entities); } async countAndWrapValueFromcapabilityArgs(args) { const count = await this.count(args); return { [constants_1.EngineConstants.dataSource.countResult]: { [constants_1.PROP_ENTITY_VALUE]: count, [constants_1.PROP_ENTITY_TYPE]: rmlmapper_js_1.XSD.integer } }; } async existsAndWrapValueFromcapabilityArgs(args) { const exists = await this.exists(args); return { [constants_1.EngineConstants.dataSource.existsResult]: { [constants_1.PROP_ENTITY_VALUE]: exists, [constants_1.PROP_ENTITY_TYPE]: rmlmapper_js_1.XSD.boolean } }; } async findAndExecuteCapability(capabilityId, args, capabilityConfig) { const capability = (await this.findBy({ id: capabilityId })); return await this.executeCapability(capability, args, capabilityConfig); } async executeParallelMapping(mapping, args, capabilityConfig) { const parallelCapabilityMappings = (0, Util_1.ensureArray)(mapping[constants_1.EngineConstants.prop.parallel]); const nestedReturnValues = await Promise.all(parallelCapabilityMappings.map((capabilityMapping) => this.executeMapping(capabilityMapping, args, capabilityConfig))); return nestedReturnValues.flat(); } async findCapabilityIntegrationMapping(capabilityId, integratedProductId) { return (await this.findByIfExists({ type: constants_1.EngineConstants.spec.capabilityMapping, [constants_1.EngineConstants.prop.capability]: capabilityId, [constants_1.EngineConstants.prop.integration]: integratedProductId })); } async performOperationMappingWithArgs(args, mapping, capabilityConfig) { if (mapping[constants_1.EngineConstants.prop.operationId]) { return { [constants_1.EngineConstants.prop.operationId]: mapping[constants_1.EngineConstants.prop.operationId] }; } if (mapping[constants_1.EngineConstants.prop.dataSource]) { return { [constants_1.EngineConstants.prop.dataSource]: mapping[constants_1.EngineConstants.prop.dataSource] }; } return await this.performMapping(args, mapping[constants_1.EngineConstants.prop.operationMapping], undefined, capabilityConfig); } async performOperation(operationInfo, operationArgs, originalArgs, account, capabilityConfig, securityCredentials) { if (operationInfo[constants_1.EngineConstants.prop.schemeName]) { return await this.performOauthSecuritySchemeStageWithCredentials(operationInfo, operationArgs, account, securityCredentials); } if (operationInfo[constants_1.EngineConstants.prop.dataSource]) { return await this.getDataFromDataSource((0, rmlmapper_js_1.getIdFromNodeObjectIfDefined)(operationInfo[constants_1.EngineConstants.prop.dataSource]), capabilityConfig); } if (operationInfo[constants_1.EngineConstants.prop.operationId]) { const response = await this.performOpenapiOperationWithCredentials((0, Util_1.getValueIfDefined)(operationInfo[constants_1.EngineConstants.prop.operationId]), operationArgs, account, capabilityConfig); return this.axiosResponseAndParamsToOperationResponse(response, operationArgs, originalArgs); } throw new Error('Operation not supported.'); } axiosResponseAndParamsToOperationResponse(response, operationParameters, originalArgs) { return { operationParameters, originalCapabilityParameters: originalArgs, data: response.data, status: response.status, statusText: response.statusText, headers: response.headers, config: { headers: response.config.headers, method: response.config.method, url: response.config.url, data: response.config.data } }; } async performReturnValueMappingWithFrameIfDefined(returnValue, mapping, capabilityConfig) { if (constants_1.EngineConstants.prop.outputsMapping in mapping) { return await this.performMapping(returnValue, mapping[constants_1.EngineConstants.prop.outputsMapping], (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.outputsMappingFrame]), capabilityConfig); } return returnValue; } async performParameterMappingOnArgsIfDefined(args, mapping, capabilityConfig, convertToJsonDeep = false) { if (constants_1.EngineConstants.prop.inputsReference in mapping) { const reference = (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.inputsReference]); return this.getDataAtReference(reference, args); } if (constants_1.EngineConstants.prop.inputsMappingRef in mapping) { const reference = (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.inputsMappingRef]); const referencedMapping = this.getDataAtReference(reference, args); // Handle inputsMappingFrameRef if present let frame; if (constants_1.EngineConstants.prop.inputsMappingFrameRef in mapping) { const frameReference = (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.inputsMappingFrameRef]); frame = this.getDataAtReference(frameReference, args); } else { // Use direct frame if provided frame = (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.inputsMappingFrame]); } // Perform mapping with the referenced mapping and frame const mappedData = await this.performMapping(args, referencedMapping, frame, capabilityConfig); return (0, Util_1.toJSON)(mappedData, convertToJsonDeep); } if (constants_1.EngineConstants.prop.inputsMapping in mapping) { const mappedData = await this.performMapping(args, mapping[constants_1.EngineConstants.prop.inputsMapping], (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.inputsMappingFrame]), capabilityConfig); return (0, Util_1.toJSON)(mappedData, convertToJsonDeep); } return args; } getDataAtReference(reference, data) { const results = (0, jsonpath_plus_1.JSONPath)({ path: reference, json: data, resultType: 'value' }); const isArrayOfLengthOne = Array.isArray(results) && results.length === 1; let result = isArrayOfLengthOne ? results[0] : results; if (result && typeof result === 'object' && constants_1.PROP_ENTITY_VALUE in result) { result = result[constants_1.PROP_ENTITY_VALUE]; } return result; } async getOpenApiDescriptionForIntegratedProduct(integratedProductId) { const openApiDescriptionSchema = await this.findBy({ type: constants_1.EngineConstants.spec.restfulApi, [constants_1.EngineConstants.prop.type]: constants_1.EngineConstants.spec.openApi, [constants_1.EngineConstants.prop.integration]: integratedProductId }); return (0, Util_1.getValueIfDefined)(openApiDescriptionSchema[constants_1.EngineConstants.prop.declarativeApiDescription]); } async findSecurityCredentialsForAccountIfDefined(accountId) { return await this.findByIfExists({ type: constants_1.EngineConstants.spec.integrationAuthenticationCredential, [constants_1.EngineConstants.prop.account]: accountId }); } async findgetOpenApiRuntimeAuthorizationCapabilityIfDefined() { return (await this.findByIfExists({ type: constants_1.EngineConstants.spec.capability, [constants_1.EngineConstants.prop.label]: constants_1.OPEN_API_RUNTIME_AUTHORIZATION })); } async getRuntimeCredentialsWithSecurityCredentials(securityCredentials, integrationId, openApiOperationInformation, operationArgs) { const getOpenApiRuntimeAuthorizationCapability = await this.findgetOpenApiRuntimeAuthorizationCapabilityIfDefined(); if (!getOpenApiRuntimeAuthorizationCapability) { return {}; } const mapping = await this.findCapabilityIntegrationMapping(getOpenApiRuntimeAuthorizationCapability[constants_1.PROP_ENTITY_ID], integrationId); if (!mapping) { return {}; } const args = { securityCredentials, openApiExecutorOperationWithPathInfo: openApiOperationInformation, operationArgs }; const operationInfoJsonLd = await this.performParameterMappingOnArgsIfDefined(args, mapping, undefined, true); const headers = (0, Util_1.getValueIfDefined)(operationInfoJsonLd[constants_1.EngineConstants.prop.headers]); return headers ?? {}; } async createOpenApiOperationExecutorWithSpec(openApiDescription) { const executor = new openapi_operation_executor_1.OpenApiOperationExecutor(); await executor.setOpenapiSpec(openApiDescription); return executor; } async findCapabilityNounMapping(capabilityId, object) { return (await this.findByIfExists({ type: constants_1.EngineConstants.spec.capabilityMapping, [constants_1.EngineConstants.prop.capability]: capabilityId, [constants_1.EngineConstants.prop.object]: (0, InversePath_1.InversePath)({ subPath: (0, ZeroOrMorePath_1.ZeroOrMorePath)({ subPath: constants_1.RDFS.subClassOf }), value: object }) })); } async performCapabilityMappingWithArgs(args, mapping, capabilityConfig) { if (mapping[constants_1.EngineConstants.prop.capabilityId]) { return (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.capabilityId]); } const capabilityInfoJsonLd = await this.performMapping(args, mapping[constants_1.EngineConstants.prop.capabilityMapping], undefined, capabilityConfig); return (0, Util_1.getValueIfDefined)(capabilityInfoJsonLd[constants_1.EngineConstants.prop.capabilityId]); } async assertCapabilityParamsMatchParameterSchemas(capabilityParams, capability) { let parametersSchemaObject = capability[constants_1.EngineConstants.prop.inputs]; if (parametersSchemaObject?.[constants_1.PROP_ENTITY_ID] && Object.keys(parametersSchemaObject).length === 1) { parametersSchemaObject = await this.findBy({ id: parametersSchemaObject[constants_1.PROP_ENTITY_ID] }); } if (capabilityParams && parametersSchemaObject) { const capabilityParamsAsJsonLd = { '@context': (0, Util_1.getValueIfDefined)(capability[constants_1.EngineConstants.prop.inputsContext]), [constants_1.PROP_ENTITY_TYPE]: constants_1.EngineConstants.spec.inputs, ...capabilityParams }; const report = await this.convertToQuadsAndValidateAgainstShape(capabilityParamsAsJsonLd, parametersSchemaObject); if (!report.conforms) { this.throwValidationReportError(report, `${(0, Util_1.getValueIfDefined)(capability[constants_1.EngineConstants.prop.label])} parameters do not conform to the schema`); } } } async performOpenapiOperationWithCredentials(operationId, operationArgs, account, capabilityConfig) { const integratedProductId = account[constants_1.EngineConstants.prop.integration][constants_1.PROP_ENTITY_ID]; const openApiDescription = await this.getOpenApiDescriptionForIntegratedProduct(integratedProductId); const openApiExecutor = await this.createOpenApiOperationExecutorWithSpec(openApiDescription); // eslint-disable-next-line @typescript-eslint/await-thenable const openApiOperationInformation = await openApiExecutor.getOperationWithPathInfoMatchingOperationId(operationId); const securityCredentials = await this.findSecurityCredentialsForAccountIfDefined(account[constants_1.PROP_ENTITY_ID]); let runtimeAuthorization = {}; if (securityCredentials) { const generatedRuntimeCredentials = await this.getRuntimeCredentialsWithSecurityCredentials(securityCredentials, integratedProductId, openApiOperationInformation, operationArgs); if (generatedRuntimeCredentials && Object.keys(generatedRuntimeCredentials).length > 0) { runtimeAuthorization = generatedRuntimeCredentials; } } const apiKey = [ (0, Util_1.getValueIfDefined)(securityCredentials?.[constants_1.EngineConstants.prop.apiKey]), this.getAuthorizationHeaderFromRuntimeCredentials(runtimeAuthorization) ].find(Boolean); const configuration = { accessToken: (0, Util_1.getValueIfDefined)(securityCredentials?.[constants_1.EngineConstants.prop.accessToken]), bearerToken: (0, Util_1.getValueIfDefined)(securityCredentials?.[constants_1.EngineConstants.prop.bearerToken]), apiKey, basePath: (0, Util_1.getValueIfDefined)(account[constants_1.EngineConstants.prop.overrideBasePath]), username: (0, Util_1.getValueIfDefined)(securityCredentials?.[constants_1.EngineConstants.prop.clientId]), password: (0, Util_1.getValueIfDefined)(securityCredentials?.[constants_1.EngineConstants.prop.clientSecret]) }; let response; let executeOperationOptions; try { const additionalHeaders = this.getHeadersFromRuntimeCredentials(runtimeAuthorization); if (additionalHeaders && typeof additionalHeaders === 'object' && !Array.isArray(additionalHeaders) && Object.keys(additionalHeaders).length > 0) { executeOperationOptions = { headers: additionalHeaders }; } if (this.ifCapabilityStreaming(capabilityConfig)) { executeOperationOptions = { ...executeOperationOptions, responseType: 'stream' }; } response = await openApiExecutor.executeOperation(operationId, configuration, operationArgs, executeOperationOptions); } catch (error) { if (axios_1.default.isAxiosError(error) && await this.isInvalidTokenError(error, integratedProductId) && securityCredentials) { const refreshedConfiguration = await this.refreshSecurityCredentials(securityCredentials, integratedProductId, account); response = await openApiExecutor.executeOperation(operationId, refreshedConfiguration, operationArgs, executeOperationOptions); } else { throw error; } } return response; } getHeadersFromRuntimeCredentials(runtimeCredentials) { let returnValue = {}; if (runtimeCredentials.headers && typeof runtimeCredentials.headers === 'object' && Object.keys(runtimeCredentials.headers).length > 0 && !Array.isArray(runtimeCredentials.headers)) { returnVa