execution-engine
Version:
A TypeScript library for tracing and visualizing code execution workflows.
268 lines (267 loc) • 13.9 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const executionEngineDecorators_1 = require("./executionEngineDecorators");
describe('decorators', () => {
describe('an alternative usage of the ExecutionEngine that illustrates the integration of decorators', () => {
it('should apply the engine decorator to a class', () => {
const id = 'testId';
let TestClass = class TestClass extends executionEngineDecorators_1.EngineTask {
};
TestClass = __decorate([
(0, executionEngineDecorators_1.engine)({ id })
], TestClass);
const instance = new TestClass();
expect(instance.engine).toBeDefined();
});
it('should apply the run decorator to a method', async () => {
let TestClass = class TestClass extends executionEngineDecorators_1.EngineTask {
async testMethod() {
return 'Test Result';
}
};
__decorate([
(0, executionEngineDecorators_1.run)()
], TestClass.prototype, "testMethod", null);
TestClass = __decorate([
(0, executionEngineDecorators_1.engine)()
], TestClass);
const instance = new TestClass();
const result = await instance.testMethod();
expect(instance.engine.getOptions().executionId).toEqual(expect.stringMatching(/^exec.*$/));
expect(result).toBe('Test Result');
});
it('should apply the run decorator with options to a method', async () => {
const id = 'greetingId';
let GreetingTask = class GreetingTask extends executionEngineDecorators_1.EngineTask {
generateGreeting(person, greeter, nodeData) {
this.engine.pushNarratives(nodeData.id, [`here is tracing narrative for greeting ${person.name}`]);
return {
greeting: {
fr: `Hello, ${person.name}`,
es: `¡Hola, ${person.name}!`,
en: `Hello, ${person.name}!`
},
greeter: `I'm ${greeter.name}.`,
hobbies: [`Let's explore the world of ${person.hobbies.join(', ')} together!`],
get fullGreeting() {
return [this.greeting.en, this.greeter, ...this.hobbies].join(' ');
}
};
}
};
__decorate([
(0, executionEngineDecorators_1.run)({
config: {
traceExecution: {
inputs: ['0.name', '0.age', '0.address.city', '1.name'],
outputs: (out) => `the output I want to trace is: '${out['fullGreeting']}'`,
errors: true,
narratives: true,
startTime: true,
endTime: false
}
}
})
], GreetingTask.prototype, "generateGreeting", null);
GreetingTask = __decorate([
(0, executionEngineDecorators_1.engine)({ id })
], GreetingTask);
const instance = new GreetingTask();
const result = instance.generateGreeting({
name: 'John Doe',
age: 30,
isStudent: false,
grades: [85, 90, 78, 95],
address: {
street: '123 Main Street',
city: 'Cityville',
zipcode: '12345'
},
contact: {
email: 'john.doe@example.com',
phone: '+1 555-1234'
},
hobbies: ['reading', 'traveling', 'coding'],
isActive: true,
birthday: '1992-05-15',
isMarried: null
}, {
name: 'Akram'
});
const greetingTrace = instance.engine.getTrace();
expect(instance.engine.getOptions().executionId).toEqual('greetingId');
expect(greetingTrace?.length).toEqual(1);
expect(greetingTrace[0]).toEqual({
data: {
id: expect.stringMatching(/^generateGreeting_.*$/),
label: expect.any(String),
inputs: [{ '0.name': 'John Doe' }, { '0.age': 30 }, { '0.address.city': 'Cityville' }, { '1.name': 'Akram' }],
outputs: "the output I want to trace is: 'Hello, John Doe! I'm Akram. Let's explore the world of reading, traveling, coding together!'",
errors: undefined,
startTime: expect.any(Date),
endTime: undefined,
duration: undefined,
elapsedTime: undefined,
parallel: undefined,
abstract: false,
createTime: expect.any(Date),
narratives: ['here is tracing narrative for greeting John Doe']
},
group: 'nodes'
});
expect(result.greeter).toEqual("I'm Akram.");
expect(result.greeting).toEqual({ en: 'Hello, John Doe!', es: '¡Hola, John Doe!', fr: 'Hello, John Doe' });
});
});
describe('advanced example with custom parameters', () => {
let MyWeatherTask = class MyWeatherTask extends executionEngineDecorators_1.EngineTask {
async fetchCurrentTemperature(city) {
return Promise.resolve(`Current Temperature in ${city}: 25°C`);
}
async fetchDailyForecast(city) {
return Promise.resolve(`Daily Forecast in ${city}: Sunny`);
}
async recommendation(city) {
const vigilanceTask = new MyVigilanceTask();
return Promise.all([vigilanceTask.decideIfIShouldGoOut(city), vigilanceTask.decideIfIShouldGoOutNextYear(city)]);
}
};
__decorate([
(0, executionEngineDecorators_1.run)()
], MyWeatherTask.prototype, "fetchCurrentTemperature", null);
__decorate([
(0, executionEngineDecorators_1.run)()
], MyWeatherTask.prototype, "fetchDailyForecast", null);
__decorate([
(0, executionEngineDecorators_1.run)()
], MyWeatherTask.prototype, "recommendation", null);
MyWeatherTask = __decorate([
(0, executionEngineDecorators_1.engine)({ id: 'whetherEngine' })
], MyWeatherTask);
let MyVigilanceTask = class MyVigilanceTask extends executionEngineDecorators_1.EngineTask {
async decideIfIShouldGoOut(city) {
const temperature = await new MyWeatherTask().fetchCurrentTemperature(city);
const forecast = await new MyWeatherTask().fetchDailyForecast(city);
const color = this.engine.run(() => 'GREEN', [temperature, forecast], {
trace: { label: 'color' },
config: { parallel: true, traceExecution: true }
})?.outputs;
const decision = this.engine.run(() => 'go out', [temperature, forecast], {
trace: { label: 'decide' },
config: { parallel: true, traceExecution: true }
})?.outputs;
return Promise.resolve(`As daily Forecast in ${city} is ${forecast} and the temperature is ${temperature}, vigilance is ${color} and you can ${decision}`);
}
async decideIfIShouldGoOutNextYear(city) {
throw new Error(`Next year too far!, could not decide for ${city}`);
}
validateDecision(stringDecision) {
return stringDecision?.includes('GREEN');
}
};
__decorate([
(0, executionEngineDecorators_1.run)({
trace: { id: 'decideIfIShouldGoOut_custom_id', narratives: ['Narrative 0 GoOut'] },
config: {
parallel: true,
traceExecution: { inputs: true, outputs: true, narratives: ['Narrative 1 GoOut', 'Narrative 2 GoOut'] }
}
})
], MyVigilanceTask.prototype, "decideIfIShouldGoOut", null);
__decorate([
(0, executionEngineDecorators_1.run)({ config: { parallel: true, errors: 'catch', traceExecution: true } })
], MyVigilanceTask.prototype, "decideIfIShouldGoOutNextYear", null);
__decorate([
(0, executionEngineDecorators_1.run)()
], MyVigilanceTask.prototype, "validateDecision", null);
MyVigilanceTask = __decorate([
(0, executionEngineDecorators_1.engine)({ id: 'whetherEngine' })
], MyVigilanceTask);
let MyIndependantVigilanceTask = class MyIndependantVigilanceTask extends MyVigilanceTask {
};
MyIndependantVigilanceTask = __decorate([
(0, executionEngineDecorators_1.engine)({ id: 'VigilanceValidationEngine' })
], MyIndependantVigilanceTask);
it('should create a trace of consecutive user-related actions', async () => {
const myWeatherTaskInstance = new MyWeatherTask();
await myWeatherTaskInstance.fetchCurrentTemperature('Monastir');
await myWeatherTaskInstance.fetchDailyForecast('Monastir');
const response = await myWeatherTaskInstance.recommendation('Monastir');
const myWeatherTaskInstanceTraceBeforeValidation = myWeatherTaskInstance.engine.getTrace();
//call validation:
const myVigilanceInstance = new MyVigilanceTask();
myVigilanceInstance.validateDecision(response[0]);
const myWeatherTaskInstanceTraceAfterValidation = myWeatherTaskInstance.engine.getTrace();
const myVigilanceInstanceAfterValidation = myVigilanceInstance.engine.getTrace();
expect(myWeatherTaskInstanceTraceBeforeValidation?.length).toEqual(14);
expect(myWeatherTaskInstanceTraceAfterValidation?.length).toEqual(16);
expect(myVigilanceInstanceAfterValidation).toEqual(myWeatherTaskInstanceTraceAfterValidation);
//call independent validation: as it is decorated with engineID: "VigilanceValidationEngine"
const myValidationTask = new MyIndependantVigilanceTask();
myValidationTask.validateDecision(response[0]);
const myValidationIndependentTrace = myValidationTask.engine.getTrace();
expect(myValidationIndependentTrace?.length).toEqual(1);
});
});
describe('deepWorkflow', () => {
let MyDeepEngineTask = class MyDeepEngineTask extends executionEngineDecorators_1.EngineTask {
constructor(depth = 10, sequence = 5) {
super();
this.depth = depth;
this.sequence = sequence;
this.currDepth = depth;
this.currSequence = sequence;
}
runInDepth(param) {
const res = this.runSequence(this.currDepth);
if (this.currDepth > 0) {
this.currDepth = this.currDepth - 1;
return this.runInDepth(res);
}
this.currDepth = this.depth;
return `result1 for ${param} at level ${this.currDepth}`;
}
runSequence(depth) {
const res = [];
while (this.currSequence > 0) {
res.push(this.task(this.sequence));
this.currSequence = this.currSequence - 1;
}
this.currSequence = this.sequence;
res.push(`finished runSequenceInDepth for depth : ${depth}`);
return res;
}
task(param) {
return `run for sequence: ${param}`;
}
};
__decorate([
(0, executionEngineDecorators_1.run)()
], MyDeepEngineTask.prototype, "runInDepth", null);
__decorate([
(0, executionEngineDecorators_1.run)()
], MyDeepEngineTask.prototype, "runSequence", null);
__decorate([
(0, executionEngineDecorators_1.run)()
], MyDeepEngineTask.prototype, "task", null);
MyDeepEngineTask = __decorate([
(0, executionEngineDecorators_1.engine)({ id: 'complexEngineId' })
], MyDeepEngineTask);
it('should create a trace of a deep workflow with consecutive tasks', async () => {
const myDeepEngineTask = new MyDeepEngineTask(10, 5);
myDeepEngineTask.runInDepth(['starting']);
const trace = myDeepEngineTask.engine.getTrace();
const nodesFromTrace = trace?.filter((tracePart) => tracePart.group === 'nodes');
expect(trace?.length).toEqual(131);
expect(nodesFromTrace)?.toEqual(myDeepEngineTask.engine.getTraceNodes());
nodesFromTrace?.forEach((n) => expect(n.data.inputs).toBeDefined());
nodesFromTrace?.forEach((n) => expect(n.data.outputs).toBeDefined());
});
});
});
;