diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b9b0e..f934fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Unitflow changelog +## 1.0.3 - 2024-01-19 + +* Gracefully stop a flow when at least one unit inside it isn't a function, informing on console + ## 1.0.2 - 2024-01-19 * Updated how ESM and CommonJS versions are distributed diff --git a/dist/unitflow.cjs b/dist/unitflow.cjs index 48b74ec..eaaabfd 100644 --- a/dist/unitflow.cjs +++ b/dist/unitflow.cjs @@ -1 +1 @@ -"use strict";exports.Unitflow=class{constructor(e={}){this.unit={},this.flow={},this.state=e}async run(...e){const t=[],n=this.prepare(e);n.flows.reverse();for(let e=n.flows.length-1;e>=0;e--){const s=new Promise((t=>this.link_and_start(n.flows[e],n,t)));t.push(s)}return Promise.all(t)}prepare(e){let t=e.pop();const n={};return"string"==typeof t?(e.push(t),t={}):this.dependents(t,n),{flows:e,dependencies:t,dependents:n,execution:{}}}dependents(e,t){for(let[n,s]of Object.entries(e))for(let e=s.length-1;e>=0;e--)t[s[e]]||(t[s[e]]=[]),t[s[e]].push(n)}link_and_start(e,t,n){const s=this.flow[e],i=s.length,o={},r=this.state;for(let c=i-1;c>=0;c--){const d=this.unit[s[c]].bind(this),p=c==i-1?n:o[c+1],l=e+":"+s[c],h=async()=>d(r,p,n);o[c]=()=>this.wait_or_run(l,h,t)}o[0]()}wait_or_run(e,t,n,s=!1){if(!s&&!this.dependencies_completed(e,n))return n.execution[e]=()=>this.wait_or_run(e,t,n,!0);n.execution[e]=!0,t(),this.check_dependents(e,n)}dependencies_completed(e,t){const n=t.dependencies[e];return!n||n.every((e=>!0===t.execution[e]))}check_dependents(e,t){const n=t.dependents[e];if(n)for(let e=n.length-1;e>=0;e--){const s=n[e];"function"==typeof t.execution[s]&&(this.dependencies_completed(s,t)&&t.execution[s]())}}}; +"use strict";exports.Unitflow=class{constructor(e={}){this.unit={},this.flow={},this.state=e}async run(...e){const t=[],n=this.prepare(e);n.flows.reverse();for(let e=n.flows.length-1;e>=0;e--){const s=new Promise((t=>this.link_and_start(n.flows[e],n,t)));t.push(s)}return Promise.all(t)}prepare(e){let t=e.pop();const n={};return"string"==typeof t?(e.push(t),t={}):this.dependents(t,n),{flows:e,dependencies:t,dependents:n,execution:{}}}dependents(e,t){for(let[n,s]of Object.entries(e))for(let e=s.length-1;e>=0;e--)t[s[e]]||(t[s[e]]=[]),t[s[e]].push(n)}link_and_start(e,t,n){const s=this.flow[e],o=s.length,i={},r=this.state;for(let c=o-1;c>=0;c--){if("function"!=typeof this.unit[s[c]])return console.log(`Error: Unit "${s[c]}" on flow "${e}" is not a function. This flow execution is stopped.`)&&n();const d=this.unit[s[c]].bind(this),l=c==o-1?n:i[c+1],p=e+":"+s[c],u=async()=>d(r,l,n);i[c]=()=>this.wait_or_run(p,u,t)}i[0]()}wait_or_run(e,t,n,s=!1){if(!s&&!this.dependencies_completed(e,n))return n.execution[e]=()=>this.wait_or_run(e,t,n,!0);n.execution[e]=!0,t(),this.check_dependents(e,n)}dependencies_completed(e,t){const n=t.dependencies[e];return!n||n.every((e=>!0===t.execution[e]))}check_dependents(e,t){const n=t.dependents[e];if(n)for(let e=n.length-1;e>=0;e--){const s=n[e];"function"==typeof t.execution[s]&&(this.dependencies_completed(s,t)&&t.execution[s]())}}}; diff --git a/dist/unitflow.mjs b/dist/unitflow.mjs index 333db32..eefb13e 100644 --- a/dist/unitflow.mjs +++ b/dist/unitflow.mjs @@ -1 +1 @@ -class e{constructor(e={}){this.unit={},this.flow={},this.state=e}async run(...e){const t=[],n=this.prepare(e);n.flows.reverse();for(let e=n.flows.length-1;e>=0;e--){const s=new Promise((t=>this.link_and_start(n.flows[e],n,t)));t.push(s)}return Promise.all(t)}prepare(e){let t=e.pop();const n={};return"string"==typeof t?(e.push(t),t={}):this.dependents(t,n),{flows:e,dependencies:t,dependents:n,execution:{}}}dependents(e,t){for(let[n,s]of Object.entries(e))for(let e=s.length-1;e>=0;e--)t[s[e]]||(t[s[e]]=[]),t[s[e]].push(n)}link_and_start(e,t,n){const s=this.flow[e],i=s.length,o={},r=this.state;for(let c=i-1;c>=0;c--){const d=this.unit[s[c]].bind(this),p=c==i-1?n:o[c+1],h=e+":"+s[c],l=async()=>d(r,p,n);o[c]=()=>this.wait_or_run(h,l,t)}o[0]()}wait_or_run(e,t,n,s=!1){if(!s&&!this.dependencies_completed(e,n))return n.execution[e]=()=>this.wait_or_run(e,t,n,!0);n.execution[e]=!0,t(),this.check_dependents(e,n)}dependencies_completed(e,t){const n=t.dependencies[e];return!n||n.every((e=>!0===t.execution[e]))}check_dependents(e,t){const n=t.dependents[e];if(n)for(let e=n.length-1;e>=0;e--){const s=n[e];"function"==typeof t.execution[s]&&(this.dependencies_completed(s,t)&&t.execution[s]())}}}export{e as Unitflow}; +class e{constructor(e={}){this.unit={},this.flow={},this.state=e}async run(...e){const t=[],n=this.prepare(e);n.flows.reverse();for(let e=n.flows.length-1;e>=0;e--){const s=new Promise((t=>this.link_and_start(n.flows[e],n,t)));t.push(s)}return Promise.all(t)}prepare(e){let t=e.pop();const n={};return"string"==typeof t?(e.push(t),t={}):this.dependents(t,n),{flows:e,dependencies:t,dependents:n,execution:{}}}dependents(e,t){for(let[n,s]of Object.entries(e))for(let e=s.length-1;e>=0;e--)t[s[e]]||(t[s[e]]=[]),t[s[e]].push(n)}link_and_start(e,t,n){const s=this.flow[e],o=s.length,i={},r=this.state;for(let c=o-1;c>=0;c--){if("function"!=typeof this.unit[s[c]])return console.log(`Error: Unit "${s[c]}" on flow "${e}" is not a function. This flow execution is stopped.`)&&n();const d=this.unit[s[c]].bind(this),p=c==o-1?n:i[c+1],l=e+":"+s[c],h=async()=>d(r,p,n);i[c]=()=>this.wait_or_run(l,h,t)}i[0]()}wait_or_run(e,t,n,s=!1){if(!s&&!this.dependencies_completed(e,n))return n.execution[e]=()=>this.wait_or_run(e,t,n,!0);n.execution[e]=!0,t(),this.check_dependents(e,n)}dependencies_completed(e,t){const n=t.dependencies[e];return!n||n.every((e=>!0===t.execution[e]))}check_dependents(e,t){const n=t.dependents[e];if(n)for(let e=n.length-1;e>=0;e--){const s=n[e];"function"==typeof t.execution[s]&&(this.dependencies_completed(s,t)&&t.execution[s]())}}}export{e as Unitflow}; diff --git a/package.json b/package.json index 31d1907..3fea99e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@alumna/unitflow", - "version": "1.0.2", + "version": "1.0.3", "description": "Organize your library or project, defining flows composable by a sequence of units", "exports": { "import": "./dist/unitflow.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e44ff4f..2b84600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 dev: true /@babel/code-frame@7.23.5: @@ -67,7 +67,7 @@ packages: dependencies: '@babel/types': 7.23.6 '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 jsesc: 2.5.2 dev: true @@ -490,7 +490,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 '@types/node': 20.11.5 chalk: 4.1.2 collect-v8-coverage: 1.0.2 @@ -524,7 +524,7 @@ packages: resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 callsites: 3.1.0 graceful-fs: 4.2.11 dev: true @@ -555,7 +555,7 @@ packages: dependencies: '@babel/core': 7.23.7 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -590,7 +590,7 @@ packages: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 dev: true /@jridgewell/resolve-uri@3.1.1: @@ -607,15 +607,15 @@ packages: resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.21: - resolution: {integrity: sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==} + /@jridgewell/trace-mapping@0.3.22: + resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 @@ -2376,7 +2376,7 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.21 + '@jridgewell/trace-mapping': 0.3.22 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 dev: true diff --git a/src/unitflow.js b/src/unitflow.js index 66d411d..75bd716 100644 --- a/src/unitflow.js +++ b/src/unitflow.js @@ -66,6 +66,10 @@ export class Unitflow { for ( let i = length - 1; i >= 0; i-- ) { + // ensure it's a function + if ( typeof this.unit[ units[i] ] !== 'function' ) + return console.log( `Error: Unit "${units[i]}" on flow "${flow}" is not a function. This flow execution is stopped.` ) && resolve(); + const unit = this.unit[ units[i] ].bind( this ), next = i == ( length - 1 ) ? resolve : sequence[ i + 1 ], name = flow + ':' + units[i], diff --git a/test/without-dependencies.test.js b/test/without-dependencies.test.js index aa8ba68..4c2f75c 100644 --- a/test/without-dependencies.test.js +++ b/test/without-dependencies.test.js @@ -1,4 +1,5 @@ import { Unitflow } from './../src/unitflow'; +import { jest } from '@jest/globals'; describe( 'Flows without dependencies', () => { @@ -73,4 +74,33 @@ describe( 'Flows without dependencies', () => { }); + test('3. Error when an unit isn\'t a function', async () => { + + // console.log mock + let log_message; + const log = jest.spyOn(console, "log").mockImplementation( message => log_message = message ); + + const lib = new Unitflow(); + + expect( lib.state ).toEqual( {} ); + + lib.unit[ 'task 1' ] = function ( state, next ) { + state[ 'task 1' ] = 'done' + next(); + } + + lib.unit[ 'task 2' ] = 'This string is not a function'; + + lib.flow[ 'tasks' ] = [ 'task 1', 'task 2' ] + + await lib.run( 'tasks' ) + + expect( lib.state ).toEqual({}); + expect( log_message ).toBe( 'Error: Unit "task 2" on flow "tasks" is not a function. This flow execution is stopped.' ) + + // undo console.log mock + log.mockReset(); + + }); + }); \ No newline at end of file