UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

914 lines 73 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 PerformanceLogger_1 = require("./util/PerformanceLogger"); 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) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.find', async () => { const context = { entities: [], operation: 'find', operationParameters: { options }, sklEngine: this }; 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)}`); } const updatedContext = { ...context, entities: [entity] }; const afterHookResult = await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.AFTER, updatedContext, entity); return afterHookResult || entity; } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.READ, globalHooks_1.HookStages.ERROR, context, error); throw error; } }, { options }); } async findBy(where, notFoundErrorMessage) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.findBy', async () => { const context = { entities: [], operation: 'findBy', operationParameters: { where }, sklEngine: this }; 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, context, error); throw error; } }, { where }); } async findByIfExists(options) { try { const entity = await this.findBy(options); return entity; } catch { return undefined; } } async findAll(options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.findAll', async () => { const context = { entities: [], operation: 'findAll', operationParameters: { options }, sklEngine: this }; 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; } }, { options }); } async groupBy(options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.groupBy', async () => { const context = { entities: [], operation: 'groupBy', operationParameters: { options }, sklEngine: this }; 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; } }, { options }); } async findAllBy(where) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.findAllBy', async () => { const context = { entities: [], operation: 'findAllBy', operationParameters: { where }, sklEngine: this }; 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; } }, { where }); } async exists(options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.exists', async () => this.queryAdapter.exists(options), { options }); } async count(options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.count', async () => this.queryAdapter.count(options), { options }); } async save(entityOrEntities, options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.save', async () => { const entityArray = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; const isSingleEntity = !Array.isArray(entityOrEntities); await globalHooks_1.globalHooks.executeBeforeCreate(entityArray, { sklEngine: this, bypassHooks: options?.bypassHooks }); try { await this.validateEntitiesConformToObjectSchema(entityArray); const savedEntities = await this.queryAdapter.save(entityArray); await globalHooks_1.globalHooks.executeAfterCreate(savedEntities, { sklEngine: this, bypassHooks: options?.bypassHooks }); return isSingleEntity ? savedEntities[0] : savedEntities; } catch (error) { await globalHooks_1.globalHooks.executeErrorCreate(entityArray, error, { sklEngine: this, bypassHooks: options?.bypassHooks }); throw error; } }, { entityCount: Array.isArray(entityOrEntities) ? entityOrEntities.length : 1 }); } async update(idOrIds, attributes, options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.update', async () => { const idArray = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; const isSingleEntity = !Array.isArray(idOrIds); await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.BEFORE, { entities: [], operation: 'update', operationParameters: { idArray, attributes }, sklEngine: this, bypassHooks: options?.bypassHooks }); try { if (idArray.length > 1) { await this.validateEntitiesWithIdsConformsToObjectSchemaForAttributes(idArray, attributes); } else { await this.validateEntityWithIdConformsToObjectSchemaForAttributes(idArray[0], attributes); } await this.queryAdapter.update(isSingleEntity ? idArray[0] : idArray, attributes); await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.AFTER, { entities: [], operation: 'update', operationParameters: { idArray, attributes }, sklEngine: this, bypassHooks: options?.bypassHooks }); } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.UPDATE, globalHooks_1.HookStages.ERROR, { entities: [], operation: 'update', operationParameters: { idArray, attributes }, sklEngine: this, bypassHooks: options?.bypassHooks }, error); throw error; } }, { idCount: Array.isArray(idOrIds) ? idOrIds.length : 1 }); } async validateEntitiesConformToObjectSchema(entities) { const entitiesByType = this.groupEntitiesByType(entities); for (const type of Object.keys(entitiesByType)) { const object = await this.findByIfExists({ id: type }); if (object) { const parentObjects = await this.getSuperClassesOfObject(type); for (const currentObject of [object, ...parentObjects]) { const entitiesOfType = entitiesByType[type]; const nounSchemaWithTarget = { ...currentObject, [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 ${currentObject[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 getSuperClassesOfObject(object) { return await this.getParentsOfSelector(object); } async getSuperClassesOfObjects(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 validateEntityConformsToObjectSchema(entity) { const nounIds = Array.isArray(entity[constants_1.PROP_ENTITY_TYPE]) ? entity[constants_1.PROP_ENTITY_TYPE] : [entity[constants_1.PROP_ENTITY_TYPE]]; const directObjects = await this.findAllBy({ id: (0, In_1.In)(nounIds) }); if (directObjects.length > 0) { const existingObjectIds = directObjects.map((object) => object[constants_1.PROP_ENTITY_ID]); const parentObjects = await this.getSuperClassesOfObjects(existingObjectIds); for (const currentObject of [...directObjects, ...parentObjects]) { const nounSchemaWithTarget = { ...currentObject, [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 ${currentObject[constants_1.PROP_ENTITY_ID]} schema.`); } } } } async validateEntitiesWithIdsConformsToObjectSchemaForAttributes(ids, attributes) { for (const id of ids) { await this.validateEntityWithIdConformsToObjectSchemaForAttributes(id, attributes); } } async getObjectsAndParentObjectsOfEntity(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 validateEntityWithIdConformsToObjectSchemaForAttributes(id, attributes) { const nouns = await this.getObjectsAndParentObjectsOfEntity(id); for (const currentObject of nouns) { if (constants_1.SHACL.property in currentObject) { const nounProperties = (0, Util_1.ensureArray)(currentObject[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 ${currentObject[constants_1.PROP_ENTITY_ID]} schema.`); } } } } } async delete(idOrIds, options) { return PerformanceLogger_1.PerformanceLogger.withSpanRoot('SklEngine.delete', async () => { const idArray = Array.isArray(idOrIds) ? idOrIds : [idOrIds]; await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.BEFORE, { entities: [], operation: 'delete', operationParameters: { idArray }, sklEngine: this, bypassHooks: options?.bypassHooks }); try { await this.queryAdapter.delete(idArray); await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.AFTER, { entities: [], operation: 'delete', operationParameters: { idArray }, sklEngine: this, bypassHooks: options?.bypassHooks }); } catch (error) { await globalHooks_1.globalHooks.execute(globalHooks_1.HookTypes.DELETE, globalHooks_1.HookStages.ERROR, { entities: [], operation: 'delete', operationParameters: { idArray }, sklEngine: this, bypassHooks: options?.bypassHooks }, error); throw error; } }, { idCount: Array.isArray(idOrIds) ? idOrIds.length : 1 }); } 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) { const mappingArray = (0, Util_1.ensureArray)(mapping); const codeBlocks = mappingArray.filter((mappingItem) => mappingItem[constants_1.PROP_ENTITY_TYPE] === constants_1.EngineConstants.spec.codeBlock && this.isJavaScriptCode((0, Util_1.getValueIfDefined)(mappingItem[constants_1.EngineConstants.prop.codeBody]))); // FIXME: Handle if we can combine the codeb blocks with triples map. // As of now if there is any code block, triples map does not get executed. if (codeBlocks.length > 0) { return await this.executeCodeBlocks(codeBlocks, args, 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, { sklEngine: this }); } 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, { sklEngine: this }); } 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, { sklEngine: this }); } 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.object) { const mapping = await this.findCapabilityObjectMapping(capabilityId, args.object); 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.capabilityMapping, [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.object) { throw new Error(`Mapping between object ${args.object} 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 (EngineConstants.prop.capability in mapping || EngineConstants.prop.capabilityMapping in mapping) { // const capabilityId = await this.performCapabilityMappingWithArgs(args, mapping, capabilityConfig); // const mappedArgs = await this.performParameterMappingOnArgsIfDefined( // { ...args, capabilityId }, // mapping as MappingWithInputs, // capabilityConfig // ); // 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 integration = mapping[constants_1.EngineConstants.prop.integration]?.[constants_1.PROP_ENTITY_ID]; // If the mapping has an integration, it means that the operation is an integration operation if (integration) { const operationInfo = await this.performOperationMappingWithArgs(originalArgs, mapping, capabilityConfig); const response = await this.performOperation(operationInfo, mappedArgs, originalArgs, account, capabilityConfig); if (!this.ifCapabilityStreaming(capabilityConfig)) { logger_1.Logger.getInstance().log('Original response', JSON.stringify(response)); } return response; } // If the mapping does not have an integration, it means that the operation is a capability operation return await this.executeCapabilityMapping(mapping, originalArgs, mappedArgs, capabilityConfig); } 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); } if (capabilityId === 'https://skl.ai/capability/execute-code') { const codeBlocks = (0, Util_1.ensureArray)(capabilityMapping[constants_1.EngineConstants.prop.codeBlocks] ?? []).filter((mappingItem) => mappingItem[constants_1.PROP_ENTITY_TYPE] === constants_1.EngineConstants.spec.codeBlock && this.isJavaScriptCode((0, Util_1.getValueIfDefined)(mappingItem[constants_1.EngineConstants.prop.codeBody]))); return await this.executeCodeBlocks(codeBlocks, 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); if (!referencedMapping || referencedMapping?.length === 0) { return 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 getIntegrationInterface(integratedProductId, integrationType = constants_1.EngineConstants.spec.integrationInterface) { if (integrationType === constants_1.EngineConstants.spec.integrationInterface) { const integrationInterface = await this.findBy({ type: integrationType, [constants_1.EngineConstants.prop.type]: constants_1.EngineConstants.spec.openApi, [constants_1.EngineConstants.prop.integration]: integratedProductId }); return integrationInterface; } // Add support for other integration types return null; } async findSecurityCredentialsForAccountIfDefined(accountId) { return await this.findByIfExists({ type: constants_1.EngineConstants.spec.integrationAuthenticationCredential, [constants_1.EngineConstants.prop.accountOrUser]: 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 findCapabilityObjectMapping(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.operationId]) { return (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.operationId]); } if (mapping[constants_1.EngineConstants.prop.operationId]) { return (0, Util_1.getValueIfDefined)(mapping[constants_1.EngineConstants.prop.operationId]); } const capabilityInfoJsonLd = await this.performMapping(args, mapping[constants_1.EngineConstants.prop.operationMapping], undefined, capabilityConfig); return (0, Util_1.getValueIfDefined)(capabilityInfoJsonLd[constants_1.EngineConstants.prop.operationId]); } 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 restAPIInterface = await this.getIntegrationInterface(integratedProductId); if (!restAPIInterface) { throw new Error(`No integration interface found for integrated product ${integratedProductId}`); } const openApiDescription = (0, Util_1.getValueIfDefined)(restAPIInterface[constants_1.EngineConstants.prop.declarativeApiDescription]); 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.getRuntimeCredentialsWithSecurityCred