{"id":24106177,"url":"https://github.com/mikesparr/redis-workflow","last_synced_at":"2025-05-12T13:22:56.417Z","repository":{"id":57159001,"uuid":"133543881","full_name":"mikesparr/redis-workflow","owner":"mikesparr","description":"Simple Promise based multi-channel workflow rules engine with Redis backing using EventEmitter","archived":false,"fork":false,"pushed_at":"2018-11-29T21:54:56.000Z","size":136,"stargazers_count":5,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-19T09:08:52.587Z","etag":null,"topics":["flow","process-manager","pubsub","redis","rules","workflow"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/mikesparr.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-05-15T16:28:38.000Z","updated_at":"2024-10-11T04:02:01.000Z","dependencies_parsed_at":"2022-09-07T12:03:00.984Z","dependency_job_id":null,"html_url":"https://github.com/mikesparr/redis-workflow","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikesparr%2Fredis-workflow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikesparr%2Fredis-workflow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikesparr%2Fredis-workflow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikesparr%2Fredis-workflow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikesparr","download_url":"https://codeload.github.com/mikesparr/redis-workflow/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253745339,"owners_count":21957352,"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":["flow","process-manager","pubsub","redis","rules","workflow"],"created_at":"2025-01-10T21:18:08.167Z","updated_at":"2025-05-12T13:22:56.387Z","avatar_url":"https://github.com/mikesparr.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redis Workflow\nDynamic rules engine to allow configurable workflow in app without requiring code changes. \nUsing Redis as backing service, you can design workflows and whenever you `start` your workflow, \nit will load your stored workflow(s) then attach a `pubsub` listener to Redis. Any time your \n`pubsub` channel message appears, it will parse the event object, perform conditional logic, and if true \nemit one or more defined actions.\n\nThis app is loosely-based on popular enterprise systems workflow rules or business process features.\n * Trigger (or event) - something happened\n * Conditions (or filters) - criteria to take action\n * Actions (or tasks) - what to do\n\n# Requirements\nYou must have Redis server running. This app treats redis like a persistent backup. It uses `pubsub` as a listener \nfor trigger events. It uses it to save and load workflows to avoid loss. The `set` and `get` workflows methods \njust interact with memory and not database. See the `IWorkflowManager` interface source for more.\n\n# Extensibility\nMost non-public properties and methods are `protected` so you may sub-class and override. `RedisWorkflowManager` is my \npreferred implementation but you could also write a new manager class that implements `IWorkflowManager` interface.\n\n# Installation\n```bash\nnpm install redis-workflow\nyarn add redis-workflow\n```\n\n# Test\nThe test script in `package.json` preprocesses the `.ts` file and then executes.\n\n`npm test` (also can run `npm run coverage`)\n\n# Usage\nThe source was written in Typescript, yet it compiles to Javascript (`npm run build`). You can use in ES5 or later supported environments. The following code snippets are implemented in the `__tests__` folder.\n\n## Quick start (Node)\n```javascript\nconst redis = require(\"redis\");\nconst flow = require(\"redis-workflow\");\n\n// instantiate\nconst config = flow.RedisConfig(\"localhost\", 6379, null, null);\nconst client = redis.createClient(); // optionally pass into manager 2nd param to share\nconst manager = new flow.RedisWorkflowManager(config);\n\n// create first workflow\nconst trigger = new flow.Trigger(\"myTrigger\");\nconst rule = new flow.Rule(\"myRule\", \"foo == bar\");\nconst action = new flow.ImmediateAction(\"myAction\");\nconst workflow = new flow.Workflow(\"myWorkflow\", trigger, [rule], [action]);\n\n// add workflow to manager\nmanager.setWorkflows({\"myChannel\": [workflow]});\n\n// add listener for action\nmanager.on(\"myAction\", (action) =\u003e {\n    // perform something here\n    console.log({action}); // should see after pubsub event below\n});\n\n// add error handler\nmanager.on(\"error\", (error) =\u003e {\n    // handle errors here\n    console.error(`An error occurred: `, error);\n})\n\n// start workflow engine\nmanager.start(\"myChannel\");\n\n// publish a test object to the pubsub channel\nsetTimeout(() =\u003e {\n    const event = {event: \"myTrigger\", context: {foo: \"bar\"}};\n    client.publish(\"myChannel\", JSON.stringify(event), (err, reply) =\u003e {\n        console.log({err, reply});\n    });\n}, 2500);\n\n// sometime later ...\nsetTimeout(() =\u003e {\n    manager.stop(\"myChannel\"); // unsubscribe from pubsub and stop emitting actions\n}, 5000);\n\n// note, if you publish an event and conditions do not == true, no action taken\n```\n\n## Typescript\nSee more detailed examples in the respective `src/__tests__` and `src/lib/__tests__` folders.\n\n### Example with multiple rules and actions for a workflow\n```typescript\nimport * as redis from \"redis\";\nimport * as flow from \"redis-workflow\"; // optionally import each class (see __tests__)\n\nconst config: flow.RedisConfig = new flow.RedisConfig(\"localhost\", 6379, null, null);\nconst manager: flow.IWorkflowManager = new flow.RedisWorkflowManager(config);\nconst publisher: redis.RedisClient = new redis.createClient(); // just for our example to pubsub message\n\n// build test workflow\nconst trigger: flow.ITrigger = new flow.Trigger(\"test.trigger101\");\nconst rule1: flow.IRule = new flow.Rule(\"Foo should equal bar\", `foo == \"bar\"`);\nconst rule2: flow.IRule = new flow.Rule(\"Should be in stock\", \"inStock \u003e 0\");\nconst action1: flow.IAction = new flow.DelayedAction(\"shipProduct\").delay(5, \"days\"); // context added later when triggered\nconst action2: flow.IAction = new flow.ImmediateAction(\"adjustInventory\");\nconst workflow: flow.IWorkflow = new flow.Workflow(\"test.workflow1\", trigger, [rule1, rule2], [action1, action2]);\n\n// add first workflow to manager\nmanager.setWorkflows({\"myChannel\": [workflow]});\n\n// errors\nmanager.on(WorkflowEvents.Error, (error) =\u003e {\n    console.error(`Something bad happened: `, error);\n})\n\n// delayed actions\nmanager.on(WorkflowEvents.Schedule, (action) =\u003e {\n    // handle delayed actions how you like (use your own scheduler)\n    console.log(`Delayed action received!`);\n\n    switch (action.getName()) {\n        case \"shipProduct\":\n            // schedule with fulfillment\n            break;\n        default:\n            // add to cron\n            break;\n    }\n});\n\n// immediate actions\nmanager.on(WorkflowEvents.Immediate, (action) =\u003e {\n    // handle immediate actions\n    console.log(`Immediate action received!`);\n\n    switch (action.getName()) {\n        case \"adjustInventory\":\n            // task inventory management\n            break;\n        default:\n            // global handler or notification\n            break;\n    }\n});\n\n// optionally handle actions by name\nmanager.on(\"adjustInventory\", (action) =\u003e {\n    // do something here\n    const item: string = action.getContext().foo;\n    const inStock: number = action.getContext().inStock;\n    console.log(`Adjusting inventory for '${item}' from ${inStock} to ${inStock - 1}`);\n});\n\n// optionally handle global actions (all types)\nmanager.on(WorkflowEvents.Audit, (action) =\u003e {\n    // publish action to stream pipeline\n});\n\n// optionally handle actions that didn't meet rules\nmanager.on(WorkflowEvents.Invalid, (message) =\u003e {\n    // do something\n    switch (message.event) {\n        case \"newOrder\":\n            // notify cust svc to try and complete order\n            break;\n        default:\n            // log somewhere\n            break;\n    }\n});\n\n// start manager (subscribes to pubsub channel)\nmanager.start(\"babyDivision\")\n    .then(() =\u003e {\n        // do something if you like\n    });\n\n// build and publish trigger events to Redis pubsub channel\nconst message: {[key: string]: any} = {\n    context: {\n        foo: \"bar\",\n        inStock: 3,\n    },\n    event: \"test.trigger101\",\n};\n\n// simulate time after manager starts before triggers appear\nsetTimeout(() =\u003e {\n    publisher.publish(\"babyDivision\", JSON.stringify(message), (err: Error, reply: any) =\u003e {\n        // simulate time later shutting down workflow channel\n        setTimeout(() =\u003e {\n            manager.stop(\"babyDivision\");\n        }, 3000);\n    });\n}, 3000);\n```\n\n## Triggers\nUses Redis `pubsub` listening for events to start workflow.\n\n```typescript\nconst trigger: ITrigger = new Trigger(\"test\");\n```\n\n### Payload\nThe `message` published to the topic will include a stringified JSON object as follows.\n\n```javascript\n{\n    \"event\": \"test\", // must equal Trigger name\n    \"context\": {\"myField\": \"myValue\", \"anotherField\": \"anotherValue\"}\n}\n```\n\n## Conditions\nUses `mozjexl` Javascript expression language to evaluate string expressions, evaluating to `true` or `false`.\n\n```typescript\nconst rule: IRule = new Rule(\"Field must be valid\", `myField == \"myValue\"`);\n```\n\n## Actions\nYou define actions when building workflows. The action name will become an `EventEmitter` event you handle.\n\n```typescript\n// optionally named\nmanager.on(\"eventName\", (action) =\u003e {\n    // handle action here\n});\n\nmanager.on(flow.WorkflowEvents.Schedule, (action) =\u003e {\n    // handle action here\n});\n\nmanager.on(flow.WorkflowEvents.Immediate, (action) =\u003e {\n    // handle action here\n});\n```\n\nFor each `Action` you add to your workflow, you one or more listeners like above. You can decide what functionality \nyour application performs if conditions are met, and actions are emitted.\n\n### Suggested action types\n * Create record(s)\n * Update record(s)\n * Trigger another workflow\n * Send message(s) or notification(s)\n\n# Scaling\nThis implementation using `pubsub` can scale by leveraging different channels per instance, with a fanout. If you \nwant workers, I would override the `start` method to use Redis `blrpoplpush` (blocking pop of list) and publish \ntrigger events to it instead. This way you can spin up unlimited workers and once one pops from list, others ignore.\n\n# Resilience\nIf you instantiate the manager and pass in the third optional argument `channels: string[]`, the app will attempt \nto load workflows from the database.\n\n# Contributing\nI haven't thought that far ahead yet. I needed this for my project and wanted to give back. ;-)\n\n# License\nMIT (if you enhance it, fork and PR so the community benefits)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikesparr%2Fredis-workflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikesparr%2Fredis-workflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikesparr%2Fredis-workflow/lists"}