@comake/skl-js-engine
Version:
Standard Knowledge Language Javascript Engine
914 lines • 73 kB
JavaScript
"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