@comake/skl-js-engine
Version:
Standard Knowledge Language Javascript Engine
950 lines • 67.2 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 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