UNPKG

execution-engine

Version:

A TypeScript library for tracing and visualizing code execution workflows.

268 lines (267 loc) 13.9 kB
"use strict"; 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()); }); }); });