{"id":19874504,"url":"https://github.com/strongloop/flow-engine","last_synced_at":"2025-10-13T20:42:11.255Z","repository":{"id":18870455,"uuid":"67075364","full_name":"strongloop/flow-engine","owner":"strongloop","description":null,"archived":false,"fork":false,"pushed_at":"2022-04-28T15:52:36.000Z","size":1574,"stargazers_count":19,"open_issues_count":3,"forks_count":7,"subscribers_count":15,"default_branch":"develop","last_synced_at":"2025-06-09T23:06:34.084Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/strongloop.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-31T21:39:31.000Z","updated_at":"2024-11-05T01:07:46.000Z","dependencies_parsed_at":"2022-07-25T06:47:00.163Z","dependency_job_id":null,"html_url":"https://github.com/strongloop/flow-engine","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/strongloop/flow-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strongloop%2Fflow-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strongloop%2Fflow-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strongloop%2Fflow-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strongloop%2Fflow-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strongloop","download_url":"https://codeload.github.com/strongloop/flow-engine/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strongloop%2Fflow-engine/sbom","scorecard":{"id":855532,"data":{"date":"2025-08-11","repo":{"name":"github.com/strongloop/flow-engine","commit":"99a77a71ea15b3404f7b0e5d256618f0c285ea16"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.6,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 2/18 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 21 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T23:48:58.797Z","repository_id":18870455,"created_at":"2025-08-23T23:48:58.798Z","updated_at":"2025-08-23T23:48:58.798Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279016930,"owners_count":26085911,"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","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-12T16:23:43.041Z","updated_at":"2025-10-13T20:42:11.241Z","avatar_url":"https://github.com/strongloop.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# flow-engine\n\nThe flow-engine is a node.js module specializing in processing of a sequence of\ntasks. The tasks execution sequence is defined in JSON object's `assembly`\nproperty and passed to flow-engine for execution.\nIn The following example, flow-engine executes three tasks - `if`, `if`, and\n`response`. \n\n```\n{\n  \"assembly\": {\n    \"execute\": [\n      {\n        \"if\": {\n          \"condition\": \"'$(request.verb)' === 'POST'\",\n          \"execute\": [\n            {\n              \"message-mediation\": {\n                \"target\": \"context.response\",\n                \"value\": \"{ status: 200, message: 'This is a POST response' }\"\n              }\n            }\n          ]\n        }\n      },\n      {\n        \"if\": {\n          \"condition\": \"'$(request.verb)' === 'GET'\",\n          \"execute\": [\n            {\n              \"message-mediation\": {\n                \"target\": \"context.response\",\n                \"value\": \"{ status: 200, message: 'This is a GET response' }\"\n              }\n            }\n          ]\n        }\n      },\n      {\n        \"response\": {\n          \"payload\": \"context.response\"\n        }\n      }\n    ]\n  }\n}\n```\n\nEach task is a JavaScript function that can\n  - execute any code\n  - make change to the `context` object\n  - instruct flow-engine to continue or stop the remaining tasks execution\n\nThe flow-engine provides some prebuilt tasks at [lib/task](lib/task).\nYou can use these prebuilt tasks to compose your flow or use them as references\nto build customized ones. Putting the customized tasks to the `lib/task`\ndirectory, and flow-engine can pick up the new tasks without additional\nconfiguration.\n\n# flow-engine and IBM microgateway\nThe flow-engine is designed to be the heart of a gateway or reverse proxy.\nIt is included and used as part of the microgateway module that\nserves as API policies enforcement point.\n\nEach API policy on the microgateway is implemented as a flow-engine\ntask. And what and when API policies need to be enforced is describe as the\nassembly flow to be executed by the flow-engine.\n\n#Installation\n`npm install flow-engine`\n\n#APIs\nTo use the FlowEngine, one must `require('flow-engine')`.\n\n- setup function `require('flow-engine')(configObj)`:  \n  configObj supports the follow options:\n  - flow : the flow definition file and it shall be a YAML file\n  - paramResolver: a module's **location** that is used to resolve the\n    placeholder inside the flow assembly\n  - tasks: an object that contains all custom task modules' name and\n    **location**\n  - baseDir: the base directory that will be used to load modules\n\n  and the return value is a middleware handler\n  \n- `Flow(assembly, optionObj)`:  \n  Create a flow instance with the flow assembly\n  - assembly: the flow's assembly and it's JSON object\n  - optionObj: supports the following options:\n    - paramResolver: a function that is used to resolve the placeholder inside\n      the flow assembly\n    - tasks: an object that contains all custom task modules' name and their\n      handler functions. Use this option to control the creation/lifecycle of\n      task handlers by yourself. \n    - tid: transaction id. if it doesn't present, flow-engine uses tid() to get\n      tid that is maintained in flow-engine's scope.\n    - logger: a bunyan logger obj that flow-engine uses it for logging. if it\n      doesn't present, flow-engine creates its own logger.\n    - logLevel: flow-engine uses it to create its own logger if `logger` is not\n       there.\n\n- `Flow.prototype.prepare(context, next)`:  \n   Pass the `context` object and `next` function to the flow instance that you\n   create by the Flow ctor\n\n- `Flow.prototype.run()`:    \n   Start to execute the flow assembly\n\n- `Flow.prototype.subscribe(event, handler)`:    \n   Subscribe an event with an event handler. \n   - event: 'START', 'FINISH', 'ERROR' or 'pre|post:task-name'\n   - handler: a handler should be:  \n     `function(event, next)`  \n     A handler function would get the event name and a done callback(`next`). \n     The handler must call `next()` when it finishes.\n\n- `Flow.prototype.unsubscribe(event, handler)`:    \n   Remove the handler from the event's subscriber list\n\n- `tid()`:    \n   get a flow-engine scope transaction Id. Basically, it's a global counter\n   that start with 1 and increase 1 by each call.\n\nTo execute a flow described in YAML:\n\n```\nvar flow = require('flow-engine')({ config: \"flow.yaml\" });\n//the flow is a middleware handler and ready to be used\n\n```\n\nOr directly create a Flow instance with a specific JSON config\n```\nconst Flow = require('flow-engine').Flow;\nvar flow = new Flow( json, optionObj );\n//you have to manually pass the context and next into the flow and execute it\n//get context obj from somewhere or create it by yourself\n//and request and response object should be attached to the context obj\nflow.prepare(context, next);\nflow.run();\n```\n\n#APIs for Task/Policy developer\nA task is the unit of the flow assembly. flow-engine leverages tasks to fulfill\na flow assembly. When developing a custom task, flow-engine provides the\nfollowing APIs:\n\n- `invoke(assembly, options, next)`:  \n  flow-engine runs the given `assembly`. When the assembly finishes. the `next`\n  callback will be invoked. the `options` here is the same as the options in\n  `Flow(assembly, optionObj)`\n  - assembly: the flow's assembly and it's JSON object\n  - next: this callback will be invoked after the assembly finishes\n  - options: supports the following options:\n    - paramResolver: a function that is used to resolve the placeholder inside\n      the flow assembly\n    - tasks: an object that contains all custom task modules' name and handler\n      function\n    If these properties don't exist, the parent's will be used.\n- `subscribe(event, handler)`:    \n   Subscribe an event with an event handler. \n   - event: 'START', 'FINISH', 'ERROR' or 'pre|post:task-name'.\n   - handler: a handler should be:  \n     `function(event, next)`  \n     A handler function would get the event name and a done callback(`next`).\n     The handler must call `next()` when it finishes.\n\n- `unsubscribe(event, handler)`:    \n   Remove the handler from the event's subscriber list\n\n- `proceed()`:    \n   When a task finishes its job, it must call this API to tell flow-engine to\n   run the next task.\n\n- `fail(error)`:    \n   When a task finishes its job with error situation, it must call this API to\n   tell flow-engine to run the Error handing to process the error.\n\n- `stop()`:    \n   When a task finishes its job and the flow should also end, it must call this\n   API to tell flow-engine to finish the assembly execution.\n\n- `logger`:    \n   A bunyan logger object which is used for logging.\n\nThese APIs are attached to the third param of task handler interface and could\nbe used by task developer. Here is a simple task implementation:\n\n```\n'use strict';\n\nmodule.exports = function ( config ) {\n\n    return function (props, context, flow) {\n        var logger = flow.logger;\n        logger.info('ENTER mypolicy, params:', JSON.stringify(props));\n        //some business logic\n        \n        //and register a FINISH event handler\n        flow.subscribe('FINISH', function(event, next) {\n            //this would be executed when a flow finishes\n            next();\n        });\n        \n        //call next task\n        flow.proceed();\n    };\n};\n```\n\n#Sample\nUse the setup function to create a flow-engine middleware and register this\nmiddleware to handle all of the requests:\n```\nvar createFlow = require('flow-engine');\n\nvar flow = createFlow({\n        flow: \"flow.yaml\",\n        tasks: {'activity-log': './myactivity-log.js'},\n        baseDir: __dirname});\n\nvar app = express();\napp.post('/*', [ flow ]);\n\n```\n\n#The Design\n### The middleware interface\n- The interface is `function(request, response, next)`\n- The flow-engine module supports the middleware interface. It returns the\n  middleware handler:\n\n  ```\n  var middlewareHandler = require('flow-engine')(some_config);\n  middlewareHandler(request, response, next);\n  ```\n\n### The Task/Policy interface\n- Every task that is defined inside the assembly should have a corresponding\n  task module.And the task module should provide a factory function - \n  `function(config)` and return a task handler function - \n  `function(props, context, flow)`. Therefore, flow-engine will use the task\n  module like this:\n\n  ```\n  var taskHandler = require('taskModule')(task_config);\n  taskHandler(props, context, flow);\n  ```\n  \n- **Finding task/policy handlers to execute the tasks/policies that defined in\n  the assembly**:\n  - tasks under `\u003cflow-engine\u003e/lib/task` are loaded when require('flow-engine').\n    flow-engine calls these tasks' factory functions with empty config object\n    and keeps the returned task handler functions\n  - use task's name to search the task module and see if there is a task module\n    under options.tasks[taskName].\n    The options.tasks here is the options that you pass to Flow's constructor or\n    the setup function.\n    The difference is the options.tasks that you pass to the setup function will\n    be processed via the `require()`, call its factory function by passing empty\n    config object and get the task handler function. And the options.tasks that\n    you pass to Flow's constructor function shall be task handler functions\n    already.\n  - searching the task module under `\u003cflow-engine\u003e/lib/task.\n  - directly use `require` against the task's name, call its factory function\n    with empty config and get the handler function\n\n- **Execute a task/policy**:\n  - use the `Finding task handlers procedure` above to get task's handler\n    function\n  - get the properties/values that defined in the assemble for the task and use\n    paramResolver to replace the placeholder into corresponding value if exists\n  - call the task handler function we get from step 1 and pass the\n    properties/values, context obj and flow obj into it. the `flow` here is an\n    object which contains a set of APIs:\n    - .proceed(): when a task finishes its job successfully, it must call this\n      function to tell flow-engine that its job is done, please continue with\n      next task\n    - .fail(error): when a task couldn't finish it job, it must call this\n      function to tell flow-engine that an error is found and ask flow-engine\n      to handle the error. flow-engine will perform the `Error Handling`\n      procedure then.\n    - .subscribe(event, cb): a task could subscribe some specific events.\n      flow-engine would invoke the `cb` when the specific events are triggered.\n      The `cb` shall be a `function(event, next)`. Inside the callback, next()\n      must be called when the callback finishes. Currently, the following events\n      are supported:\n      - FINISH: a flow finishes. flow-engine finishes an assembly execution\n      - ERROR: an error is threw from an assembly execution\n      - pre|post:taskName: before or after a task's execution\n    - .unsubscribe(event, cb): unsubscribe an event\n    - .logger: a bunyan logger object and could be used for logging\n  - if the task finishes without any error, then the flow engine goes to the\n    next task\n\n- **Error Handling**:\n  - check if there is local error handling for the task:\n    - if yes, then execute the tasks defined in the error handling:\n      - when the local error handling finishes and the local handling throws\n        another error, then go to global error handling\n      - then the flow ends\n    - if no, then see if there is any global error handling:\n      - no global error handling : use default error handling and then ends the\n        flow\n      - global error handling exists: execute the global error handling and then\n        ends the flow\n\n### Event\nflow-engine supports observable interface and provides `subscribe()` and\n`unsubscribe()` APIs. Currently, the following events are supported:\n\n- START: flow-engine starts to execute an assembly and none of the policy is\n  executed yet\n- FINISH: all of the policies in an assembly are executed successfully\n- ERROR: the flow execution ends with an error\n- 'pre|post:task-name': before or after the execution of a task/policy.\n  The post event is **only** triggered when a task/policy finishes without any\n  error, even if the 'catch' clause recovers the error.\n\nIn order to keep the flow execution is clean, any error that is thrown/created\nby an event handler would only be logged and then ignored. Therefore, an event\nhandler wouldn't impact the flow execution as well as other event handlers.\n\nYou can access the `subscribe()` and `unsubscribe()` from a Flow instance if\nyou use the Flow ctor to create a flow instance. In side a task, the third\nparam of the task handler function is an flow object which contains\n`subscribe()` and `unsubscribe()` APIs.\n\n### The `context` object\nThe `context` object should be created before the flow-engine, probably by\nusing a context middleware before the flow-engine middleware. flow-engine uses\nthe `context` object as one of the arguments when invoking every task function.\nA task could access `context` object, including retrieving and populating\nproperties. When flow-engine finishes, all the information should be stored into\nthe `context` object. Having a middleware after the flow-engine middleware is a\ntypical approach to produce the output content and maybe flush/write to the\nresponse object at the same time. \n\nCurrently, flow-engine provides a context module in `./lib/context` which could\nbe used to create the context object. It provides the following APIs:\n- createContext([namespace]):  \n  create a context object with the specified namespace. namespace is optional.\n\n- The context object provides getter, setter and dot notation to access its\n  variables:\n  - get(name):  \n    get the variable by name.\n\n  - set(name, value[, readOnly]):    \n    add or update a variable with the specified name and value. default value\n    of readOnly is 'false'.\n    Use readonly=true to add read-only variable.  \n    Note: updating a read-only variable will cause the exception.\n\n  - define(name, getter[,setter, configurable]):\n    - `name`: variable name\n    - `getter`: getter function\n    - `setter`: setter function\n    - `configurable`: allow re-define or not. (true|false)\n\n    add or update a variable with the specified getter, setter and configurable.\n    Use configurable=false to avoid variable re-define.  \n    Note: set new value to a getter-only variable will cause the exception.\n\n  - del(name):  \n    delete the variable by name.\n\n  - subscribe(event, handler):  \n    subscribe an event with the handler function : `function(event, next)`.\n\n  - unsubscribe(event, handler):  \n    unsubscribe the event\n\n  - notify(event, callback):  \n    fire the event and all of the handlers are invoked sequentially (based on\n    the registration sequence). Then `callback` is called with undefined or an\n    array of errors that subscriber handlers return.\n\n  - dot notation:  \n    You can also use dot notation to access the variables of a context object.\n```javascript\n        //If a context is created with a namespace - 'fool',\n        //you can access its variables like this:    \n        var ctx1 = require('./lib/context').createContext('fool');\n        ctx1.set('myvar', 'value');\n        ctx1.fool.myvar === 'value';\n        ctx1.fool.myvar = 'new-value';\n        ctx1.get('myvar') === 'new-value';\n        //\n        //If there is no namespace, then all of the variables are\n        //directly attached to the context object:\n        var ctx2 = require('./lib/context').createContext();\n        ctx2.set('myvar', 'value');\n        ctx2.myvar === 'value';\n        ctx2.myvar = 'new-value';\n        ctx2.get('myvar') === 'new-value';\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrongloop%2Fflow-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrongloop%2Fflow-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrongloop%2Fflow-engine/lists"}