{"id":13989646,"url":"https://github.com/faressoft/flowa","last_synced_at":"2025-03-23T23:30:41.596Z","repository":{"id":57238818,"uuid":"137828427","full_name":"faressoft/flowa","owner":"faressoft","description":"🔥Service level control flow for Node.js","archived":false,"fork":false,"pushed_at":"2018-09-10T18:23:53.000Z","size":364,"stargazers_count":74,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-19T00:16:59.159Z","etag":null,"topics":["api","async","control","express","expressjs","flow","javascript","nodejs","parallel","promise","restful","router","runner","serial","series","sync","task","waterfall","web"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faressoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-19T02:10:01.000Z","updated_at":"2024-03-01T15:51:24.000Z","dependencies_parsed_at":"2022-09-05T07:51:16.784Z","dependency_job_id":null,"html_url":"https://github.com/faressoft/flowa","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faressoft%2Fflowa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faressoft%2Fflowa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faressoft%2Fflowa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faressoft%2Fflowa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faressoft","download_url":"https://codeload.github.com/faressoft/flowa/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245186423,"owners_count":20574550,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","async","control","express","expressjs","flow","javascript","nodejs","parallel","promise","restful","router","runner","serial","series","sync","task","waterfall","web"],"created_at":"2024-08-09T13:01:53.815Z","updated_at":"2025-03-23T23:30:41.210Z","avatar_url":"https://github.com/faressoft.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"/logo.png?raw=true\" alt=\"Flowa Logo\"/\u003e\u003c/p\u003e\n\n# Flowa\n\n[![npm](https://img.shields.io/npm/v/flowa.svg)](https://www.npmjs.com/package/flowa)\n[![npm](https://img.shields.io/npm/l/flowa.svg)](https://github.com/faressoft/flowa/blob/master/LICENSE)\n[![Gitter](https://badges.gitter.im/join_chat.svg)](https://gitter.im/flowa-control-flow/Lobby)\n[![Build Status](https://travis-ci.org/faressoft/flowa.svg?branch=master)](https://travis-ci.org/faressoft/flowa)\n[![Coverage Status](https://coveralls.io/repos/github/faressoft/flowa/badge.svg?branch=master)](https://coveralls.io/github/faressoft/flowa?branch=master)\n\n\u003e Service level control flow for Node.js\n\nOne execution flow that runs mixed sync and async functions that uses either promises or callbacks running in parallel, sequentially or mixed. 🔥 **It can't be easier and more readable !**\n\n# Hint\n\nCheck the [suggested way](#use-it-with-express) to use `Flowa` with `Express.js`.\n\n# Demo\n\n\u003cp align=\"center\"\u003e\u003cimg width=\"100%\" src=\"/demo.gif?raw=true\" alt=\"Flowa Demo\"/\u003e\u003c/p\u003e\n\n# Table of Contents\n\n* [Features](#features)\n* [Introduction](#introduction)\n* [Installation](#installation)\n* [Usage](#usage)\n  * [Shorthand Method](#shorthand-method)\n  * [Mixed Runners Types](#mixed-runners-types)\n  * [Promises](#promises)\n  * [Sync Tasks](#sync-tasks)\n  * [Terminating The Flow](#terminating-the-flow)\n  * [Jumping Between Tasks](#jumping-between-tasks)\n  * [Error Handling](#error-handling)\n  * [Factory Method](#factory-method)\n  * [ES6 Coding Style](#es6-coding-style)\n  * [Use It With Express](#use-it-with-express)\n* [Best Practices](#best-practices)\n* [Debugging Mode](#debugging-mode)\n* [API](#api)\n  * [Flowa(flow[, name])](#constructor)\n  * [Flowa.create(flow[, name])](#create)\n  * [run(context, options)](#run)\n* [License](#license)\n\n## Features\n\n* Writing more readable code for complex logic.\n* Works with promises or callbacks.\n* Works with sync or async tasks.\n* Serial or parallel execution.\n* No more callback hells.\n* Jumping between tasks.\n* Proper error handling.\n* Timeouts.\n\n## Introduction\n\nEach `flow` is a set of `tasks`. It starts by a `compound task` which is basically a task that groups a set of `single` or other `compound` tasks. Single tasks are either async or sync functions that are executed and called by passing an object called `context` to allow sharing data between tasks and an optional `callback` function for async tasks that use callbacks instead of promises. Each compound task's sub tasks are executed by a `runner` that can be a `serial` execution (default type) or a `parallel` execution.\n\n## Installation\n\n```\nnpm install --save flowa\n```\n\n## Usage\n\nWe need to create a new Flowa object with our flow using `new Flowa(flow[, name])`, `Flowa.create(flow[, name])`, or just use the [Shorthand Method](#shorthand-method) it is much easier and recommended if you are not planning to execute the same flow again and again.\n\n```js\nvar Flowa = require('flowa');\n\n// Define the flow\nvar flowa = new Flowa({\n\n  // Runner type\n  type: 'serial',\n\n  // A task that uses a callback\n  asyncTaskWithCallback: asyncTaskWithCallback,\n\n  // A task that returns a promise\n  asyncTaskWithPromise: asyncTaskWithPromise,\n\n  // A sync task\n  syncTask: syncTask\n\n});\n```\n\nThen we need to execute the flow.\n\n```js\n// To be used to share data between the tasks\nvar context = {};\n\n// Execute the tasks\nflowa.run(context).then(function(result) {\n\n  console.log(result);\n\n}).catch(function(error) {\n\n  console.error(error);\n  \n});\n```\n\nAnd don't forget to write the code for your tasks.\n\n```js\n// A task that uses a callback\nfunction asyncTaskWithCallback(context, callback) {\n\n  setTimeout(callback.bind(null, null, 'DummyValue1'), 500);\n\n}\n\n// A task that returns a promise\nfunction asyncTaskWithPromise(context) {\n\n  return Promise.resolve('DummyValue2');\n\n}\n\n// A sync task\nfunction syncTask(context) {\n\n  return 'DummyValue3';\n\n}\n```\n\nJust put the 3 blocks of code together in one script and they will run smoothly.\n\n### Shorthand Method\n\nIs it possible to create a flow and execute it using a single function `.run()` that belongs to the Flowa class.\n\n```js\nFlowa.run({\n\n  // Runner type\n  type: 'serial',\n\n  // Do task1\n  task1: task1,\n\n  // Do task2\n  task2: task2\n\n}).then(function(result) {\n\n  console.log(result);\n\n}).catch(function(error) {\n\n  console.error(error);\n  \n});\n```\n\n### Mixed Runners Types\n\nThere are no limitations about mixing the runners types. Add `type` to the compound tasks to specify the runner type. But remember, it is not a good idea to make things too complex.\n\n\n```js\nvar flowa = new Flowa({\n\n  // Runner type\n  type: 'serial',\n  \n  // Do task1\n  task1: task1,\n\n  // Do task2 and task3 in parallel\n  group1: {\n\n    // Runner type\n    type: 'parallel',\n\n    // Do task2\n    task2: task2,\n\n    // Do task3\n    task3: task3,\n\n    // Do task4 and task5 in parallel\n    group2: {\n\n      // Runner type\n      type: 'serial',\n\n      // Do task4\n      task4: task4,\n\n      // Do task5\n      task5: task5\n\n    }\n\n  },\n\n  // Do task6\n  task6: task6\n\n});\n```\n\n### Promises\n\nYou can return promises from your tasks instead of using callbacks. The callbacks will be called internally.\n\n```js\nfunction task1(context) {\n\n  return new Promise(function(resolve, reject) {\n\n    resolve();\n\n  });\n\n}\n```\n\n### Sync Tasks\n\nYou can use sync tasks that doesn't return a promise and doesn't take a second callback argument.\n\n```js\nfunction task1(context) {\n\n  // Do something !!\n\n}\n```\n\n### Terminating The Flow\n\nYou can terminate the flow (skip executing the remaining tasks) by calling the `done` method.\n\n```js\nfunction task1(context, callback) {\n\n  this.done();\n  callback();\n\n}\n```\n\n### Jumping Between Tasks\n\nYou can jump forward and backward between tasks that belong to the same parent task and the runner type is `serial` by calling the `jump` method with the task name as first argument to jump into it after executing the current task completely. You can jump into a compound task too.\n\n```js\nfunction task1(context, callback) {\n\n  this.jump('task6');\n  callback();\n\n}\n```\n\n### Loop and Retry\n\nSince we have the ability to jump backward and forward, we can implement a task to try something and another task to check the result to decide either to jump back to the previous task or continue.\n\n```js\nfunction task1(context, callback) {\n\n  // We are just generating a random boolean value here\n  context.checkSomething = Math.random() \u003e= 0.5;\n  callback();\n\n}\n\n/**\n * Task\n * \n * @param {Object}   context\n * @param {Function} callback\n */\nfunction task2(context, callback) {\n\n  if (context.checkSomething) {\n    return callback();\n  }\n\n  // Retry\n  this.jump('task1');\n  callback();\n  \n}\n```\n\n### Error Handling\n\nThe thrown errors and the errors passed as a first argument to the callback function can be handled by attaching a `.catch()` to the returend promise from `run()` method.\n\n\n```js\n// Using callbacks (Recommended)\nfunction checkUser(context, callback) {\n  callback(new Error('User is not found'));\n}\n\n// Using the `throw` operator\nfunction checkUser(context, callback) {\n  throw new Error('User is not found');\n}\n```\n\n### Factory Method\n\nIs it possible to create a new Flowa object by calling `.create()` method instead of using `new Flowa`.\n\n```js\nFlowa.create({\n\n  // Runner type\n  type: 'serial',\n\n  // Do task1\n  task1: task1,\n\n  // Do task2\n  task2: task2\n\n}).run(context).then(function(result) {\n\n  console.log(result);\n\n}).catch(function(error) {\n\n  console.error(error);\n  \n});\n```\n\n### ES6 Coding Style\n\nYou can use the shorthand syntax for naming the tasks by their functions names.\n\n```js\nvar flowa = new Flowa({\n\n  // Runner type\n  type: 'serial',\n  \n  // Shorthand format for task1: task1\n  task1,\n\n  // Shorthand format for task2:task2\n  task2,\n\n  // Shorthand format for task3:task3\n  task3,\n\n  // Shorthand format for task4:task4\n  task4,\n\n  // Shorthand format for task5:task5\n  task5,\n\n  // Shorthand format for task6:task6\n  task6\n\n});\n```\n\n### Use It With Express\n\nYou can use `Flowa` to make more readable and maintainable `express.js` services.\n\n#### App.js\n\nTo initilize your web server and load your services.\n\n**Note**: No need to change the code, just add more services at the line 16.\n\n```js\nvar express = require('express');\nvar Flowa   = require('./index.js');\nvar app     = express();\n\n/**\n * A mapping between services names and their handlers\n * @type {Object}\n */\nvar handlers = {};\n\n/**\n * RESTful API services\n * @type {Array}\n */\nvar services = [\n  {name: 'greeting.get', path: '/greeting/:name', method: 'get'}\n];\n\n/**\n * Get a function to handle a specific service\n * \n * @param  {String}   name the name of the service\n * @return {Function}\n */\nfunction getServiceHandler(name) {\n\n  return function(req, res) {\n\n    var handler = handlers[name];\n    var context = {req: req, res: res};\n\n    handler.run(context).then(function() {\n\n      res.end();\n\n    }).catch(function(error) {\n\n      if (res.headersSent) {\n        return res.end();\n      }\n\n      res.status(500).send({\n        error: 'Something went wrong !'\n      });\n\n      console.error(error);\n\n    });\n    \n  };\n\n}\n\n// Foreach service, define its route and attach a handler\nservices.forEach(function(route) {\n\n  handlers[route.name] = new Flowa(require('./' + route.name)),\n  app[route.method](route.path, getServiceHandler(route.name));\n  \n});\n\napp.listen(3000, console.log.bind(null, 'listening ...'));\n```\n\n#### Greeting.get.js\n\nAn example of a service.\n\n```js\n/**\n * Generate a greeting message\n * \n * @author Mohammad Fares \u003cfaressoft.com@gmail.com\u003e\n */\n\nvar counter = 0;\n\n/**\n * Increment the greeting counter\n * \n * @param {Object} context\n */\nfunction incrementGreetingCounter(context) {\n\n  context.counterValue = ++counter;\n\n}\n\n/**\n * Generate a greeting message\n * \n * @param {Object} context\n */\nfunction generateGreetingMessage(context) {\n\n  context.res.send({\n    message: 'Hello ' + context.req.params.name,\n    counter: context.counterValue\n  });\n\n}\n\nmodule.exports = {\n\n  // Runner type\n  type: 'serial',\n\n  // Increment the greeting counter\n  incrementGreetingCounter: incrementGreetingCounter,\n\n  // Generate a greeting message\n  generateGreetingMessage: generateGreetingMessage\n\n};\n\n```\n\n## Best Practices\n\n* Stick with one coding style.\n* Define your flow object in a separated object or better in a separated module.\n* Add comments for each task to get a quick overview about all the tasks at one place.\n* Each single task should do literally only one task.\n* Specifiy the runners types.\n\n## Debugging Mode\n\nTo watch how the tasks being executed in realtime, you can activate the debug logging via the `debug` option.\n\n```js\nflowa.run(context, {debug: true});\n```\n\n## API\n\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#constructor\"\u003eFlowa(flow[, name])\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eTo create Flowa objects\u003c/p\u003e\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#flowa-create\"\u003eFlowa.create(flow[, name])\u003c/a\u003e ⇒ \u003ccode\u003eFlowa\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eA factory method to create Flowa objects\u003c/p\u003e\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#flowa-run\"\u003eFlowa.run(flow[, context, options])\u003c/a\u003e ⇒ \u003ccode\u003ePromise\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eCreate a flow and execute it\u003c/p\u003e\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#flowa-instance-run\"\u003eflowa.run(context[, options])\u003c/a\u003e ⇒ \u003ccode\u003ePromise\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eExecute the flow\u003c/p\u003e\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#task-done\"\u003etask.done()\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eSkip the remaining tasks\u003c/p\u003e\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#task-jump\"\u003etask.jump(taskName)\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003eJump into another task under the same parent after executing the current task\u003c/p\u003e\u003c/dd\u003e\n\u003c/dl\u003e\n\n### Note\n\n\u003e A new instance from the class `Task` is created for each execution for each task.\n\n\u003ca name=\"constructor\"\u003e\u003c/a\u003e\n\n## Flowa(flow[, name])\n\nTo create Flowa objects.\n\n| Param | Type                | Description                    |\n|-------|---------------------|--------------------------------|\n| flow  | \u003ccode\u003eObject\u003c/code\u003e | A compound task                |\n| name  | \u003ccode\u003eString\u003c/code\u003e | A name for the flow (Optional) |\n\n\u003ca name=\"flowa-create\"\u003e\u003c/a\u003e\n\n## Flowa.create(flow[, name]) ⇒ \u003ccode\u003eFlowa\u003c/code\u003e\n\nA factory method to create Flowa objects.\n\n**Returns**: \u003ccode\u003eFlowa\u003c/code\u003e - a new Flowa object\n\n| Param | Type                | Description                    |\n|-------|---------------------|--------------------------------|\n| flow  | \u003ccode\u003eObject\u003c/code\u003e | A compound task                |\n| name  | \u003ccode\u003eString\u003c/code\u003e | A name for the flow (Optional) |\n\n\u003ca name=\"flowa-run\"\u003e\u003c/a\u003e\n\n## Flowa.run(flow[, context, options]) ⇒ \u003ccode\u003ePromise\u003c/code\u003e\n\nCreate a flow and execute it.\n\n**Returns**: \u003ccode\u003ePromise\u003c/code\u003e - resolve with the passed context object\n\n| Param   | Type                | Description                                                |\n|---------|---------------------|------------------------------------------------------------|\n| flow    | \u003ccode\u003eObject\u003c/code\u003e | A compound task                                            |\n| context | \u003ccode\u003eObject\u003c/code\u003e | A shared object between the tasks (Optional) (default: {}) |\n| options | \u003ccode\u003eObject\u003c/code\u003e | (Optional)                                                 |\n\n\u003ca name=\"flowa-instance-run\"\u003e\u003c/a\u003e\n\n## flowa.run(context, options) ⇒ \u003ccode\u003ePromise\u003c/code\u003e\n\nExecute the flow. The Flowa object can be defined once and executed as many as you need.\n\n**Returns**: \u003ccode\u003ePromise\u003c/code\u003e - resolve with the passed context object\n\n| Param   | Type                | Description                                                |\n|---------|---------------------|------------------------------------------------------------|\n| context | \u003ccode\u003eObject\u003c/code\u003e | A shared object between the tasks (Optional) (default: {}) |\n| options | \u003ccode\u003eObject\u003c/code\u003e | (Optional)                                                 |\n\n#### Options:\n\n* **timeout**: a timeout for the flow in milliseconds. The promise will be rejected with an error object that has (code: `ETIMEDOUT`) if the timeout is exeeded (type: `Number`).\n* **taskTimeout**: a timeout for the single tasks in milliseconds. The promise will be rejected with an error object that has (code: `ETIMEDOUT`) if the timeout is exeeded (type: `Number`).\n* **autoInjectResults**: Inject the result of each task into the context automatically (type: `Boolean`) (default: `true`).\n* **debug**: log the tasks' names in realtime (type: `Boolean`) (default: `false`).\n* **debugCallback**: the debug logging function (type: `Boolean`) (default: `console.log`).\n\n\u003ca name=\"task-done\"\u003e\u003c/a\u003e\n\n## task.done()\n\nSkip the remaining tasks. Check [Terminating The Flow](#terminating-the-flow).\n\n\u003ca name=\"task-jump\"\u003e\u003c/a\u003e\n\n## task.jump(taskName)\n\nJump into another task under the same parent after executing the current task. Check [Jumping Between Tasks](#jumping-between-tasks).\n\n| Param    | Type                | Description                    |\n|----------|---------------------|--------------------------------|\n| taskName | \u003ccode\u003eString\u003c/code\u003e | The name of the sibling task   |\n\n# License\n\nThis project is under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaressoft%2Fflowa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaressoft%2Fflowa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaressoft%2Fflowa/lists"}