├── .eslintrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── examples └── sample │ ├── index.ts │ ├── package-lock.json │ └── package.json ├── img └── workflow.png ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── constants │ └── index.ts ├── controllers │ ├── execution.ts │ ├── index.ts │ ├── task.ts │ └── workflow.ts ├── core.ts ├── crudable.ts ├── interpreter │ └── index.ts ├── localCrud.ts ├── microflow.ts ├── storage │ └── index.ts ├── types.ts └── utils │ ├── convert.ts │ ├── index.ts │ ├── task.ts │ └── xstate.ts ├── tests ├── atomicNodeInitial.test.ts ├── taskMapSyncNode.test.ts ├── taskNodeInitial.test.ts ├── taskSyncNode.test.ts ├── taskToken.test.ts └── utils │ └── index.test.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "rules": { 6 | "quotes": [2, "single", { "avoidEscape": true }], 7 | "@typescript-eslint/no-explicit-any": 0 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/eslint-recommended", 12 | "plugin:@typescript-eslint/recommended" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | test 4 | .DS_Store 5 | .history -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Karan Chhabra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to microflow 👋

2 |

3 | 4 | Version 5 | 6 | 7 | Documentation 8 | 9 | 10 | Maintenance 11 | 12 | 13 | License: MIT 14 | 15 |

16 | 17 | > Finite state machine based HTTP microservice orchestration 18 | 19 | ### 🏠 [Homepage](https://github.com/krn0x2/microflow#readme) 20 | 21 | ## Purpose 22 | 23 | Microflow helps you build and run complex workflows which are composed of HTTP microservices and manual (human moderator) stages, all by definiing a JSON workflow blueprint. It is built on robust concepts of __finite state machine__, and allows you to control input/output data as template variables (think jsonpath, handlebars). 24 | A workflow consists of manual states and __task__ states (aka HTTP workers which could be sync/async). 25 | 26 | License: MIT 27 | 28 | ## Install 29 | npm 30 | ```sh 31 | npm i --save microflow@alpha 32 | ``` 33 | 34 | ## Documentation 35 | 36 | The `Microflow` class provides various methods to author/execute/infer workflow and workflow instances 37 | 38 | ```javascript 39 | import { Microflow } from "microflow"; 40 | 41 | const flow = new Microflow({ 42 | jwt: { 43 | secretOrPublicKey: 'dummySecretKey', 44 | sign: { 45 | expiresIn: '1h' 46 | } 47 | } 48 | }); 49 | 50 | const { 51 | // task interface 52 | task, 53 | // workflow interface 54 | workflow, 55 | // execution interface 56 | execution, 57 | } = flow; 58 | ``` 59 | 60 | ## Usage 61 | 62 | ```javascript 63 | import { Microflow } from "microflow"; 64 | 65 | const flow = new Microflow({ 66 | // bring your own persistence here 67 | // (implements MicroflowStorage) 68 | storage: undefined, 69 | jwt: { 70 | secretOrPublicKey: 'dummySecretKey', 71 | sign: { 72 | expiresIn: '1h' 73 | } 74 | } 75 | }); 76 | 77 | // Authoring task and workflow 78 | 79 | // Register a task 80 | const task = await flow.task.create({ 81 | // Recognisiable identified for the task 82 | id: 'airflow', 83 | // type of task (only 'http' supported right now) 84 | type: 'http', 85 | // supported (https://github.com/axios/axios/blob/master/index.d.ts#L44) 86 | config: { 87 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 88 | headers: { 89 | 'Cache-Control': 'no-cache', 90 | 'Content-Type': 'application/json' 91 | }, 92 | data: { 93 | conf: { 94 | // $ is a reference to 'parameters' object of a task in the workflow 95 | actualData: '$.data', 96 | token: '$.token', 97 | envKey: '$.envKey', 98 | executionId: '$.executionId' 99 | } 100 | }, 101 | method: 'post' 102 | } 103 | }); 104 | 105 | const { id: taskId } = await task.data(); 106 | 107 | // Create a workflow 108 | const workflow = await flow.workflow.create({ 109 | id: 'sample', 110 | config: { 111 | initial: 'auto_test_1', 112 | states: { 113 | auto_test_1: { 114 | type: 'task', 115 | taskId, 116 | parameters: { 117 | //example of constant 118 | dagId: 'dag1', 119 | // $ = input event data to the task state 120 | data: '$', 121 | // $$ = Execution context object 122 | token: '$$.task.token', 123 | // $$$ = process.env aka environment variables 124 | envKey: '$$$.myKey1', 125 | executionId: '$$.executionId' 126 | }, 127 | onDone: { 128 | target: 'ready_for_approval', 129 | resultSelector: { 130 | a: 'a', 131 | b: 'b', 132 | out: '$' 133 | }, 134 | resultPath: '$.pipeline1.success' 135 | }, 136 | onError: { 137 | target: 'failed', 138 | resultSelector: { 139 | c: 'c', 140 | d: 'd', 141 | out: '$' 142 | }, 143 | resultPath: '$.pipeline1.error' 144 | } 145 | }, 146 | ready_for_approval: { 147 | type: 'atomic', 148 | on: { 149 | reject: { 150 | target: 'failed', 151 | resultPath: '$.reject.data' 152 | }, 153 | approve: { 154 | target: 'auto_test_2', 155 | resultPath: '$.approval.data' 156 | } 157 | } 158 | }, 159 | auto_test_2: { 160 | type: 'task', 161 | taskId, 162 | parameters: { 163 | dagId: 'dag2', 164 | data: '$', 165 | token: '$$.task.token', 166 | envKey: '$$$.myKey1', 167 | executionId: '$$.executionId' 168 | }, 169 | onDone: { 170 | target: 'done', 171 | resultSelector: { 172 | e: 'e', 173 | out: '$' 174 | }, 175 | resultPath: '$.pipeline2.success' 176 | }, 177 | onError: { 178 | target: 'failed', 179 | resultSelector: { 180 | f: 'f', 181 | out: '$' 182 | }, 183 | resultPath: '$.pipeline2.error' 184 | } 185 | }, 186 | done: { 187 | type: 'final' 188 | }, 189 | failed: { 190 | type: 'final' 191 | } 192 | } 193 | } 194 | }); 195 | 196 | // start an execution with initial data 197 | const execution = await workflow.start({ 198 | input1: 'val1', 199 | input2: 'val2' 200 | }); 201 | 202 | // Sending events to an execution 203 | await execution.send({ 204 | type: 'success-auto_test_1', 205 | data: { 206 | test_a_result: true, 207 | test_b_result: false 208 | } 209 | }); 210 | 211 | await execution.send({ 212 | type: 'approve', 213 | data: { 214 | message: 'The acceptance test was fine' 215 | } 216 | }); 217 | 218 | await execution.send({ 219 | type: 'success-auto_test_2', 220 | data: { 221 | test_c_result: true 222 | } 223 | }); 224 | 225 | const { completed, output, state } = await execution.data(); 226 | 227 | console.log(output); 228 | /* 229 | { 230 | "input1": "val1", 231 | "input2": "val2", 232 | "pipeline1": { 233 | "success": { 234 | "a": "a", 235 | "b": "b", 236 | "out": { 237 | "test_a_result": true, 238 | "test_b_result": false 239 | } 240 | } 241 | }, 242 | "approval": { 243 | "data": { 244 | "message": "The acceptance test was fine" 245 | } 246 | }, 247 | "pipeline2": { 248 | "success": { 249 | "e": "e", 250 | "out": { 251 | "test_c_result": true 252 | } 253 | } 254 | } 255 | } 256 | */ 257 | 258 | ``` 259 | ## Storage 260 | 261 | ```javascript 262 | import { Microflow } from "microflow"; 263 | import { MicroflowStorage } from "microflow/types"; 264 | 265 | // define your own storage from MicroflowStorage abstract class 266 | class MyStorage implements IMicroflowStorage { 267 | workflow: ICrudable; 268 | task: ICrudable; 269 | execution: ICrudable; 270 | // define CRUD functions 271 | constructor(){ 272 | this.workflow = { 273 | //define CRUD implementation here 274 | } 275 | 276 | this.task = { 277 | //define CRUD implementation here 278 | } 279 | 280 | this.execution = { 281 | //define CRUD implementation here 282 | } 283 | } 284 | } 285 | 286 | 287 | const store = new MyStorage(); 288 | 289 | // create an instance of microflow with custom store injected 290 | const flow = new Microflow({ 291 | storage: store, 292 | jwt: { 293 | secretOrPublicKey: 'dummySecretKey', 294 | sign: { 295 | expiresIn: '1h' 296 | } 297 | } 298 | }); 299 | ``` 300 | 301 | ## Examples 302 | 303 | Navigate to `examples/basic` to run a sample express project with ephemeral storage. 304 | 305 | ## Run tests 306 | 307 | ```sh 308 | npm run test 309 | ``` 310 | 311 | ## Author 312 | 313 | 👤 **Karan Chhabra** 314 | 315 | * Github: [@krn0x2](https://github.com/krn0x2) 316 | * LinkedIn: [@krn0x2](https://linkedin.com/in/krn0x2) 317 | 318 | ## 🤝 Contributing 319 | 320 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/krn0x2/microflow/issues). You can also take a look at the [contributing guide](https://github.com/krn0x2/microflow/blob/master/CONTRIBUTING.md). 321 | 322 | ## Show your support 323 | 324 | Give a ⭐️ if this project helped you! 325 | 326 | ## 📝 License 327 | 328 | Copyright © 2020 [Karan Chhabra](https://github.com/krn0x2).
329 | This project is [MIT](https://github.com/krn0x2/microflow/blob/master/LICENSE) licensed. 330 | -------------------------------------------------------------------------------- /examples/sample/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | import { Microflow } from '../../src/microflow'; 4 | 5 | const app = express(); 6 | const port = 5000; 7 | const PATH = 'microflow'; 8 | 9 | const microflowService = new Microflow({ 10 | jwt: { 11 | secretOrPublicKey: 'shhhhh', 12 | sign: { 13 | expiresIn: '24h' 14 | } 15 | } 16 | }); 17 | 18 | app.use(bodyParser.json()); 19 | 20 | // Register task 21 | app.post(`/${PATH}/task`, async (req, res) => { 22 | const { body } = req; 23 | const task = await microflowService.task.create(body); 24 | const response = await task.data(); 25 | return res.status(200).json(response); 26 | }); 27 | 28 | // Query task 29 | app.get(`/${PATH}/task/:id`, async (req, res) => { 30 | const { params } = req; 31 | const { id } = params; 32 | const task = await microflowService.task.read(id); 33 | const response = await task.data(); 34 | return res.status(200).json(response); 35 | }); 36 | 37 | // Register workflow 38 | app.post(`/${PATH}/workflow`, async (req, res) => { 39 | const { body } = req; 40 | const workflow = await microflowService.workflow.create(body); 41 | const response = await workflow.data(); 42 | return res.status(200).json(response); 43 | }); 44 | 45 | // Query workflow 46 | app.get(`/${PATH}/workflow/:id`, async (req, res) => { 47 | const { params } = req; 48 | const { id } = params; 49 | const workflow = await microflowService.workflow.read(id); 50 | const response = await workflow.data(); 51 | return res.status(200).json(response); 52 | }); 53 | 54 | // Update workflow 55 | app.put(`/${PATH}/workflow`, async (req, res) => { 56 | const { body } = req; 57 | const { id } = body; 58 | const workflow = await microflowService.workflow.update(id, body); 59 | const response = await workflow.data(); 60 | return res.status(200).json(response); 61 | }); 62 | 63 | // Start workflow instance 64 | app.post(`/${PATH}/workflow/:id/start`, async (req, res) => { 65 | const { params, body } = req; 66 | const { id } = params; 67 | const workflow = await microflowService.workflow.read(id); 68 | const execution = await workflow.start(body); 69 | const response = await execution.data(); 70 | return res.status(200).json(response); 71 | }); 72 | 73 | // Send event to workflow instance 74 | app.post(`/${PATH}/workflow/instance/:id/event`, async (req, res) => { 75 | const { params, body } = req; 76 | const { id } = params; 77 | const execution = await microflowService.execution.read(id); 78 | await execution.send(body); 79 | const response = await execution.data(); 80 | return res.status(200).json(response); 81 | }); 82 | 83 | // Send event to workflow instance 84 | app.get(`/${PATH}/task-success`, async (req, res) => { 85 | const { body, query } = req; 86 | const { token } = query; 87 | console.log(token); 88 | const execution = await microflowService.sendTaskSuccess( 89 | token as string, 90 | body 91 | ); 92 | const response = await execution.data(); 93 | return res.status(200).json(response); 94 | }); 95 | 96 | app.get(`/${PATH}/task-failure`, async (req, res) => { 97 | const { body, query } = req; 98 | const { token } = query; 99 | console.log(token); 100 | const execution = await microflowService.sendTaskFailure( 101 | token as string, 102 | body 103 | ); 104 | const response = await execution.data(); 105 | return res.status(200).json(response); 106 | }); 107 | 108 | // Query workflow instance 109 | app.get(`/${PATH}/workflow/instance/:id`, async (req, res) => { 110 | const { id } = req.params; 111 | const execution = await microflowService.execution.read(id); 112 | const response = await execution.data(); 113 | res.status(200).json(response); 114 | }); 115 | 116 | // Healthcheck 117 | app.get(`/${PATH}`, (req, res) => { 118 | res.status(200).json({ message: 'Started' }); 119 | }); 120 | 121 | app.listen(port, () => { 122 | console.log(`endpoints listening at http://localhost:${port}`); 123 | }); 124 | -------------------------------------------------------------------------------- /examples/sample/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "1.19.0", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", 10 | "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", 11 | "dev": true, 12 | "requires": { 13 | "@types/connect": "*", 14 | "@types/node": "*" 15 | } 16 | }, 17 | "@types/connect": { 18 | "version": "3.4.34", 19 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", 20 | "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", 21 | "dev": true, 22 | "requires": { 23 | "@types/node": "*" 24 | } 25 | }, 26 | "@types/express": { 27 | "version": "4.17.9", 28 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz", 29 | "integrity": "sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==", 30 | "dev": true, 31 | "requires": { 32 | "@types/body-parser": "*", 33 | "@types/express-serve-static-core": "*", 34 | "@types/qs": "*", 35 | "@types/serve-static": "*" 36 | } 37 | }, 38 | "@types/express-jwt": { 39 | "version": "0.0.42", 40 | "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", 41 | "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", 42 | "dev": true, 43 | "requires": { 44 | "@types/express": "*", 45 | "@types/express-unless": "*" 46 | } 47 | }, 48 | "@types/express-serve-static-core": { 49 | "version": "4.17.14", 50 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.14.tgz", 51 | "integrity": "sha512-uFTLwu94TfUFMToXNgRZikwPuZdOtDgs3syBtAIr/OXorL1kJqUJT9qCLnRZ5KBOWfZQikQ2xKgR2tnDj1OgDA==", 52 | "dev": true, 53 | "requires": { 54 | "@types/node": "*", 55 | "@types/qs": "*", 56 | "@types/range-parser": "*" 57 | } 58 | }, 59 | "@types/express-unless": { 60 | "version": "0.5.1", 61 | "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", 62 | "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", 63 | "dev": true, 64 | "requires": { 65 | "@types/express": "*" 66 | } 67 | }, 68 | "@types/mime": { 69 | "version": "2.0.3", 70 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", 71 | "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", 72 | "dev": true 73 | }, 74 | "@types/node": { 75 | "version": "14.14.11", 76 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", 77 | "integrity": "sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==" 78 | }, 79 | "@types/qs": { 80 | "version": "6.9.5", 81 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", 82 | "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", 83 | "dev": true 84 | }, 85 | "@types/range-parser": { 86 | "version": "1.2.3", 87 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", 88 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", 89 | "dev": true 90 | }, 91 | "@types/serve-static": { 92 | "version": "1.13.8", 93 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", 94 | "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", 95 | "dev": true, 96 | "requires": { 97 | "@types/mime": "*", 98 | "@types/node": "*" 99 | } 100 | }, 101 | "abbrev": { 102 | "version": "1.1.1", 103 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 104 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 105 | }, 106 | "accepts": { 107 | "version": "1.3.7", 108 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 109 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 110 | "requires": { 111 | "mime-types": "~2.1.24", 112 | "negotiator": "0.6.2" 113 | } 114 | }, 115 | "ajv": { 116 | "version": "6.12.6", 117 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 118 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 119 | "optional": true, 120 | "requires": { 121 | "fast-deep-equal": "^3.1.1", 122 | "fast-json-stable-stringify": "^2.0.0", 123 | "json-schema-traverse": "^0.4.1", 124 | "uri-js": "^4.2.2" 125 | } 126 | }, 127 | "ansi-regex": { 128 | "version": "2.1.1", 129 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 130 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 131 | }, 132 | "any-promise": { 133 | "version": "1.3.0", 134 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 135 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 136 | }, 137 | "aproba": { 138 | "version": "1.2.0", 139 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 140 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 141 | }, 142 | "are-we-there-yet": { 143 | "version": "1.1.5", 144 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 145 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 146 | "requires": { 147 | "delegates": "^1.0.0", 148 | "readable-stream": "^2.0.6" 149 | } 150 | }, 151 | "arg": { 152 | "version": "4.1.3", 153 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 154 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 155 | "dev": true 156 | }, 157 | "array-flatten": { 158 | "version": "1.1.1", 159 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 160 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 161 | }, 162 | "asn1": { 163 | "version": "0.2.4", 164 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 165 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 166 | "optional": true, 167 | "requires": { 168 | "safer-buffer": "~2.1.0" 169 | } 170 | }, 171 | "assert-plus": { 172 | "version": "1.0.0", 173 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 174 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 175 | "optional": true 176 | }, 177 | "async": { 178 | "version": "1.5.2", 179 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 180 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 181 | }, 182 | "asynckit": { 183 | "version": "0.4.0", 184 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 185 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 186 | "optional": true 187 | }, 188 | "aws-sign2": { 189 | "version": "0.7.0", 190 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 191 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", 192 | "optional": true 193 | }, 194 | "aws4": { 195 | "version": "1.11.0", 196 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", 197 | "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", 198 | "optional": true 199 | }, 200 | "axios": { 201 | "version": "0.21.1", 202 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", 203 | "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", 204 | "requires": { 205 | "follow-redirects": "^1.10.0" 206 | } 207 | }, 208 | "balanced-match": { 209 | "version": "1.0.0", 210 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 211 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 212 | }, 213 | "bcrypt-pbkdf": { 214 | "version": "1.0.2", 215 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 216 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 217 | "optional": true, 218 | "requires": { 219 | "tweetnacl": "^0.14.3" 220 | } 221 | }, 222 | "block-stream": { 223 | "version": "0.0.9", 224 | "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", 225 | "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", 226 | "optional": true, 227 | "requires": { 228 | "inherits": "~2.0.0" 229 | } 230 | }, 231 | "bluebird": { 232 | "version": "3.7.2", 233 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 234 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 235 | }, 236 | "body-parser": { 237 | "version": "1.19.0", 238 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 239 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 240 | "requires": { 241 | "bytes": "3.1.0", 242 | "content-type": "~1.0.4", 243 | "debug": "2.6.9", 244 | "depd": "~1.1.2", 245 | "http-errors": "1.7.2", 246 | "iconv-lite": "0.4.24", 247 | "on-finished": "~2.3.0", 248 | "qs": "6.7.0", 249 | "raw-body": "2.4.0", 250 | "type-is": "~1.6.17" 251 | }, 252 | "dependencies": { 253 | "debug": { 254 | "version": "2.6.9", 255 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 256 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 257 | "requires": { 258 | "ms": "2.0.0" 259 | } 260 | }, 261 | "ms": { 262 | "version": "2.0.0", 263 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 264 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 265 | }, 266 | "qs": { 267 | "version": "6.7.0", 268 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 269 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 270 | } 271 | } 272 | }, 273 | "brace-expansion": { 274 | "version": "1.1.11", 275 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 276 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 277 | "requires": { 278 | "balanced-match": "^1.0.0", 279 | "concat-map": "0.0.1" 280 | } 281 | }, 282 | "buffer-equal-constant-time": { 283 | "version": "1.0.1", 284 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 285 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 286 | }, 287 | "buffer-from": { 288 | "version": "1.1.1", 289 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 290 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 291 | "dev": true 292 | }, 293 | "bytes": { 294 | "version": "3.1.0", 295 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 296 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 297 | }, 298 | "caseless": { 299 | "version": "0.12.0", 300 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 301 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", 302 | "optional": true 303 | }, 304 | "chownr": { 305 | "version": "1.1.4", 306 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 307 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 308 | }, 309 | "code-point-at": { 310 | "version": "1.1.0", 311 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 312 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 313 | }, 314 | "combined-stream": { 315 | "version": "1.0.8", 316 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 317 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 318 | "optional": true, 319 | "requires": { 320 | "delayed-stream": "~1.0.0" 321 | } 322 | }, 323 | "concat-map": { 324 | "version": "0.0.1", 325 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 326 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 327 | }, 328 | "console-control-strings": { 329 | "version": "1.1.0", 330 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 331 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 332 | }, 333 | "content-disposition": { 334 | "version": "0.5.3", 335 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 336 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 337 | "requires": { 338 | "safe-buffer": "5.1.2" 339 | } 340 | }, 341 | "content-type": { 342 | "version": "1.0.4", 343 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 344 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 345 | }, 346 | "cookie": { 347 | "version": "0.4.0", 348 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 349 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 350 | }, 351 | "cookie-signature": { 352 | "version": "1.0.6", 353 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 354 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 355 | }, 356 | "core-util-is": { 357 | "version": "1.0.2", 358 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 359 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 360 | }, 361 | "create-require": { 362 | "version": "1.1.1", 363 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 364 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 365 | "dev": true 366 | }, 367 | "dashdash": { 368 | "version": "1.14.1", 369 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 370 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 371 | "optional": true, 372 | "requires": { 373 | "assert-plus": "^1.0.0" 374 | } 375 | }, 376 | "debug": { 377 | "version": "4.3.1", 378 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 379 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 380 | "requires": { 381 | "ms": "2.1.2" 382 | } 383 | }, 384 | "deep-extend": { 385 | "version": "0.6.0", 386 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 387 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 388 | }, 389 | "delayed-stream": { 390 | "version": "1.0.0", 391 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 392 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 393 | "optional": true 394 | }, 395 | "delegates": { 396 | "version": "1.0.0", 397 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 398 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 399 | }, 400 | "depd": { 401 | "version": "1.1.2", 402 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 403 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 404 | }, 405 | "destroy": { 406 | "version": "1.0.4", 407 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 408 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 409 | }, 410 | "detect-libc": { 411 | "version": "1.0.3", 412 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 413 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 414 | }, 415 | "diff": { 416 | "version": "4.0.2", 417 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 418 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 419 | "dev": true 420 | }, 421 | "dottie": { 422 | "version": "2.0.2", 423 | "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", 424 | "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" 425 | }, 426 | "ecc-jsbn": { 427 | "version": "0.1.2", 428 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 429 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 430 | "optional": true, 431 | "requires": { 432 | "jsbn": "~0.1.0", 433 | "safer-buffer": "^2.1.0" 434 | } 435 | }, 436 | "ecdsa-sig-formatter": { 437 | "version": "1.0.11", 438 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 439 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 440 | "requires": { 441 | "safe-buffer": "^5.0.1" 442 | } 443 | }, 444 | "ee-first": { 445 | "version": "1.1.1", 446 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 447 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 448 | }, 449 | "encodeurl": { 450 | "version": "1.0.2", 451 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 452 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 453 | }, 454 | "escape-html": { 455 | "version": "1.0.3", 456 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 457 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 458 | }, 459 | "etag": { 460 | "version": "1.8.1", 461 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 462 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 463 | }, 464 | "express": { 465 | "version": "4.17.1", 466 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 467 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 468 | "requires": { 469 | "accepts": "~1.3.7", 470 | "array-flatten": "1.1.1", 471 | "body-parser": "1.19.0", 472 | "content-disposition": "0.5.3", 473 | "content-type": "~1.0.4", 474 | "cookie": "0.4.0", 475 | "cookie-signature": "1.0.6", 476 | "debug": "2.6.9", 477 | "depd": "~1.1.2", 478 | "encodeurl": "~1.0.2", 479 | "escape-html": "~1.0.3", 480 | "etag": "~1.8.1", 481 | "finalhandler": "~1.1.2", 482 | "fresh": "0.5.2", 483 | "merge-descriptors": "1.0.1", 484 | "methods": "~1.1.2", 485 | "on-finished": "~2.3.0", 486 | "parseurl": "~1.3.3", 487 | "path-to-regexp": "0.1.7", 488 | "proxy-addr": "~2.0.5", 489 | "qs": "6.7.0", 490 | "range-parser": "~1.2.1", 491 | "safe-buffer": "5.1.2", 492 | "send": "0.17.1", 493 | "serve-static": "1.14.1", 494 | "setprototypeof": "1.1.1", 495 | "statuses": "~1.5.0", 496 | "type-is": "~1.6.18", 497 | "utils-merge": "1.0.1", 498 | "vary": "~1.1.2" 499 | }, 500 | "dependencies": { 501 | "debug": { 502 | "version": "2.6.9", 503 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 504 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 505 | "requires": { 506 | "ms": "2.0.0" 507 | } 508 | }, 509 | "ms": { 510 | "version": "2.0.0", 511 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 512 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 513 | }, 514 | "qs": { 515 | "version": "6.7.0", 516 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 517 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 518 | } 519 | } 520 | }, 521 | "express-jwt": { 522 | "version": "6.0.0", 523 | "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-6.0.0.tgz", 524 | "integrity": "sha512-C26y9myRjx7CyhZ+BAT3p+gQyRCoDZ7qo8plCvLDaRT6je6ALIAQknT6XLVQGFKwIy/Ux7lvM2MNap5dt0T7gA==", 525 | "requires": { 526 | "async": "^1.5.0", 527 | "express-unless": "^0.3.0", 528 | "jsonwebtoken": "^8.1.0", 529 | "lodash.set": "^4.0.0" 530 | } 531 | }, 532 | "express-unless": { 533 | "version": "0.3.1", 534 | "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", 535 | "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" 536 | }, 537 | "extend": { 538 | "version": "3.0.2", 539 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 540 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 541 | "optional": true 542 | }, 543 | "extsprintf": { 544 | "version": "1.3.0", 545 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 546 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", 547 | "optional": true 548 | }, 549 | "fast-deep-equal": { 550 | "version": "3.1.3", 551 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 552 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 553 | "optional": true 554 | }, 555 | "fast-json-stable-stringify": { 556 | "version": "2.1.0", 557 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 558 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 559 | "optional": true 560 | }, 561 | "finalhandler": { 562 | "version": "1.1.2", 563 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 564 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 565 | "requires": { 566 | "debug": "2.6.9", 567 | "encodeurl": "~1.0.2", 568 | "escape-html": "~1.0.3", 569 | "on-finished": "~2.3.0", 570 | "parseurl": "~1.3.3", 571 | "statuses": "~1.5.0", 572 | "unpipe": "~1.0.0" 573 | }, 574 | "dependencies": { 575 | "debug": { 576 | "version": "2.6.9", 577 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 578 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 579 | "requires": { 580 | "ms": "2.0.0" 581 | } 582 | }, 583 | "ms": { 584 | "version": "2.0.0", 585 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 586 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 587 | } 588 | } 589 | }, 590 | "follow-redirects": { 591 | "version": "1.13.1", 592 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", 593 | "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" 594 | }, 595 | "forever-agent": { 596 | "version": "0.6.1", 597 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 598 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", 599 | "optional": true 600 | }, 601 | "form-data": { 602 | "version": "2.3.3", 603 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 604 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 605 | "optional": true, 606 | "requires": { 607 | "asynckit": "^0.4.0", 608 | "combined-stream": "^1.0.6", 609 | "mime-types": "^2.1.12" 610 | } 611 | }, 612 | "forwarded": { 613 | "version": "0.1.2", 614 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 615 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 616 | }, 617 | "fresh": { 618 | "version": "0.5.2", 619 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 620 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 621 | }, 622 | "fs-minipass": { 623 | "version": "1.2.7", 624 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 625 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 626 | "requires": { 627 | "minipass": "^2.6.0" 628 | } 629 | }, 630 | "fs.realpath": { 631 | "version": "1.0.0", 632 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 633 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 634 | }, 635 | "fstream": { 636 | "version": "1.0.12", 637 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", 638 | "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", 639 | "optional": true, 640 | "requires": { 641 | "graceful-fs": "^4.1.2", 642 | "inherits": "~2.0.0", 643 | "mkdirp": ">=0.5 0", 644 | "rimraf": "2" 645 | } 646 | }, 647 | "gauge": { 648 | "version": "2.7.4", 649 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 650 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 651 | "requires": { 652 | "aproba": "^1.0.3", 653 | "console-control-strings": "^1.0.0", 654 | "has-unicode": "^2.0.0", 655 | "object-assign": "^4.1.0", 656 | "signal-exit": "^3.0.0", 657 | "string-width": "^1.0.1", 658 | "strip-ansi": "^3.0.1", 659 | "wide-align": "^1.1.0" 660 | } 661 | }, 662 | "getpass": { 663 | "version": "0.1.7", 664 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 665 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 666 | "optional": true, 667 | "requires": { 668 | "assert-plus": "^1.0.0" 669 | } 670 | }, 671 | "glob": { 672 | "version": "7.1.6", 673 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 674 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 675 | "requires": { 676 | "fs.realpath": "^1.0.0", 677 | "inflight": "^1.0.4", 678 | "inherits": "2", 679 | "minimatch": "^3.0.4", 680 | "once": "^1.3.0", 681 | "path-is-absolute": "^1.0.0" 682 | } 683 | }, 684 | "graceful-fs": { 685 | "version": "4.2.4", 686 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 687 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 688 | "optional": true 689 | }, 690 | "handlebars": { 691 | "version": "4.7.6", 692 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", 693 | "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", 694 | "requires": { 695 | "minimist": "^1.2.5", 696 | "neo-async": "^2.6.0", 697 | "source-map": "^0.6.1", 698 | "uglify-js": "^3.1.4", 699 | "wordwrap": "^1.0.0" 700 | } 701 | }, 702 | "har-schema": { 703 | "version": "2.0.0", 704 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 705 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", 706 | "optional": true 707 | }, 708 | "har-validator": { 709 | "version": "5.1.5", 710 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 711 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 712 | "optional": true, 713 | "requires": { 714 | "ajv": "^6.12.3", 715 | "har-schema": "^2.0.0" 716 | } 717 | }, 718 | "has-unicode": { 719 | "version": "2.0.1", 720 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 721 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 722 | }, 723 | "http-errors": { 724 | "version": "1.7.2", 725 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 726 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 727 | "requires": { 728 | "depd": "~1.1.2", 729 | "inherits": "2.0.3", 730 | "setprototypeof": "1.1.1", 731 | "statuses": ">= 1.5.0 < 2", 732 | "toidentifier": "1.0.0" 733 | }, 734 | "dependencies": { 735 | "inherits": { 736 | "version": "2.0.3", 737 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 738 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 739 | } 740 | } 741 | }, 742 | "http-signature": { 743 | "version": "1.2.0", 744 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 745 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 746 | "optional": true, 747 | "requires": { 748 | "assert-plus": "^1.0.0", 749 | "jsprim": "^1.2.2", 750 | "sshpk": "^1.7.0" 751 | } 752 | }, 753 | "iconv-lite": { 754 | "version": "0.4.24", 755 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 756 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 757 | "requires": { 758 | "safer-buffer": ">= 2.1.2 < 3" 759 | } 760 | }, 761 | "ignore-walk": { 762 | "version": "3.0.3", 763 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 764 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 765 | "requires": { 766 | "minimatch": "^3.0.4" 767 | } 768 | }, 769 | "inflection": { 770 | "version": "1.12.0", 771 | "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", 772 | "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" 773 | }, 774 | "inflight": { 775 | "version": "1.0.6", 776 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 777 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 778 | "requires": { 779 | "once": "^1.3.0", 780 | "wrappy": "1" 781 | } 782 | }, 783 | "inherits": { 784 | "version": "2.0.4", 785 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 786 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 787 | }, 788 | "ini": { 789 | "version": "1.3.5", 790 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 791 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 792 | }, 793 | "ipaddr.js": { 794 | "version": "1.9.1", 795 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 796 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 797 | }, 798 | "is-fullwidth-code-point": { 799 | "version": "1.0.0", 800 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 801 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 802 | "requires": { 803 | "number-is-nan": "^1.0.0" 804 | } 805 | }, 806 | "is-typedarray": { 807 | "version": "1.0.0", 808 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 809 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 810 | "optional": true 811 | }, 812 | "isarray": { 813 | "version": "1.0.0", 814 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 815 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 816 | }, 817 | "isexe": { 818 | "version": "2.0.0", 819 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 820 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 821 | "optional": true 822 | }, 823 | "isstream": { 824 | "version": "0.1.2", 825 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 826 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 827 | "optional": true 828 | }, 829 | "jsbn": { 830 | "version": "0.1.1", 831 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 832 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 833 | "optional": true 834 | }, 835 | "json-schema": { 836 | "version": "0.2.3", 837 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 838 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", 839 | "optional": true 840 | }, 841 | "json-schema-traverse": { 842 | "version": "0.4.1", 843 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 844 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 845 | "optional": true 846 | }, 847 | "json-stringify-safe": { 848 | "version": "5.0.1", 849 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 850 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 851 | "optional": true 852 | }, 853 | "jsonpath-plus": { 854 | "version": "4.0.0", 855 | "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-4.0.0.tgz", 856 | "integrity": "sha512-e0Jtg4KAzDJKKwzbLaUtinCn0RZseWBVRTRGihSpvFlM3wTR7ExSp+PTdeTsDrLNJUe7L7JYJe8mblHX5SCT6A==" 857 | }, 858 | "jsonwebtoken": { 859 | "version": "8.5.1", 860 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 861 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 862 | "requires": { 863 | "jws": "^3.2.2", 864 | "lodash.includes": "^4.3.0", 865 | "lodash.isboolean": "^3.0.3", 866 | "lodash.isinteger": "^4.0.4", 867 | "lodash.isnumber": "^3.0.3", 868 | "lodash.isplainobject": "^4.0.6", 869 | "lodash.isstring": "^4.0.1", 870 | "lodash.once": "^4.0.0", 871 | "ms": "^2.1.1", 872 | "semver": "^5.6.0" 873 | }, 874 | "dependencies": { 875 | "semver": { 876 | "version": "5.7.1", 877 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 878 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 879 | } 880 | } 881 | }, 882 | "jsprim": { 883 | "version": "1.4.1", 884 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 885 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 886 | "optional": true, 887 | "requires": { 888 | "assert-plus": "1.0.0", 889 | "extsprintf": "1.3.0", 890 | "json-schema": "0.2.3", 891 | "verror": "1.10.0" 892 | } 893 | }, 894 | "jwa": { 895 | "version": "1.4.1", 896 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 897 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 898 | "requires": { 899 | "buffer-equal-constant-time": "1.0.1", 900 | "ecdsa-sig-formatter": "1.0.11", 901 | "safe-buffer": "^5.0.1" 902 | } 903 | }, 904 | "jws": { 905 | "version": "3.2.2", 906 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 907 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 908 | "requires": { 909 | "jwa": "^1.4.1", 910 | "safe-buffer": "^5.0.1" 911 | } 912 | }, 913 | "lodash": { 914 | "version": "4.17.20", 915 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 916 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" 917 | }, 918 | "lodash.includes": { 919 | "version": "4.3.0", 920 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 921 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 922 | }, 923 | "lodash.isboolean": { 924 | "version": "3.0.3", 925 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 926 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 927 | }, 928 | "lodash.isinteger": { 929 | "version": "4.0.4", 930 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 931 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 932 | }, 933 | "lodash.isnumber": { 934 | "version": "3.0.3", 935 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 936 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 937 | }, 938 | "lodash.isplainobject": { 939 | "version": "4.0.6", 940 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 941 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 942 | }, 943 | "lodash.isstring": { 944 | "version": "4.0.1", 945 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 946 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 947 | }, 948 | "lodash.once": { 949 | "version": "4.1.1", 950 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 951 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 952 | }, 953 | "lodash.set": { 954 | "version": "4.3.2", 955 | "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", 956 | "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" 957 | }, 958 | "lru-cache": { 959 | "version": "6.0.0", 960 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 961 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 962 | "requires": { 963 | "yallist": "^4.0.0" 964 | } 965 | }, 966 | "make-error": { 967 | "version": "1.3.6", 968 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 969 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 970 | "dev": true 971 | }, 972 | "media-typer": { 973 | "version": "0.3.0", 974 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 975 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 976 | }, 977 | "merge-descriptors": { 978 | "version": "1.0.1", 979 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 980 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 981 | }, 982 | "methods": { 983 | "version": "1.1.2", 984 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 985 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 986 | }, 987 | "microflow": { 988 | "version": "3.0.0-alpha.2", 989 | "resolved": "https://registry.npmjs.org/microflow/-/microflow-3.0.0-alpha.2.tgz", 990 | "integrity": "sha512-tA639DyBuiaOmteZpNtPLPp3HMOZaTWPqJ0KKNdLAwnJI0+dtxbNc10FaP8u4UuogrS+VeXsagOv44HZt0CHlA==", 991 | "requires": { 992 | "axios": "^0.21.0", 993 | "bluebird": "^3.7.2", 994 | "handlebars": "^4.7.6", 995 | "jsonpath-plus": "^4.0.0", 996 | "jsonwebtoken": "^8.5.1", 997 | "lodash": "^4.17.20", 998 | "nanoid": "^3.1.20", 999 | "xstate": "^4.14.0" 1000 | } 1001 | }, 1002 | "mime": { 1003 | "version": "1.6.0", 1004 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1005 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1006 | }, 1007 | "mime-db": { 1008 | "version": "1.44.0", 1009 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 1010 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 1011 | }, 1012 | "mime-types": { 1013 | "version": "2.1.27", 1014 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 1015 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 1016 | "requires": { 1017 | "mime-db": "1.44.0" 1018 | } 1019 | }, 1020 | "minimatch": { 1021 | "version": "3.0.4", 1022 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1023 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1024 | "requires": { 1025 | "brace-expansion": "^1.1.7" 1026 | } 1027 | }, 1028 | "minimist": { 1029 | "version": "1.2.5", 1030 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1031 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 1032 | }, 1033 | "minipass": { 1034 | "version": "2.9.0", 1035 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 1036 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 1037 | "requires": { 1038 | "safe-buffer": "^5.1.2", 1039 | "yallist": "^3.0.0" 1040 | }, 1041 | "dependencies": { 1042 | "yallist": { 1043 | "version": "3.1.1", 1044 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1045 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1046 | } 1047 | } 1048 | }, 1049 | "minizlib": { 1050 | "version": "1.3.3", 1051 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 1052 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 1053 | "requires": { 1054 | "minipass": "^2.9.0" 1055 | } 1056 | }, 1057 | "mkdirp": { 1058 | "version": "0.5.5", 1059 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1060 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1061 | "requires": { 1062 | "minimist": "^1.2.5" 1063 | } 1064 | }, 1065 | "moment": { 1066 | "version": "2.29.1", 1067 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 1068 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" 1069 | }, 1070 | "moment-timezone": { 1071 | "version": "0.5.32", 1072 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", 1073 | "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", 1074 | "requires": { 1075 | "moment": ">= 2.9.0" 1076 | } 1077 | }, 1078 | "ms": { 1079 | "version": "2.1.2", 1080 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1081 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1082 | }, 1083 | "nanoid": { 1084 | "version": "3.1.20", 1085 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", 1086 | "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" 1087 | }, 1088 | "needle": { 1089 | "version": "2.5.2", 1090 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", 1091 | "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", 1092 | "requires": { 1093 | "debug": "^3.2.6", 1094 | "iconv-lite": "^0.4.4", 1095 | "sax": "^1.2.4" 1096 | }, 1097 | "dependencies": { 1098 | "debug": { 1099 | "version": "3.2.7", 1100 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 1101 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 1102 | "requires": { 1103 | "ms": "^2.1.1" 1104 | } 1105 | } 1106 | } 1107 | }, 1108 | "negotiator": { 1109 | "version": "0.6.2", 1110 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1111 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1112 | }, 1113 | "neo-async": { 1114 | "version": "2.6.2", 1115 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 1116 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" 1117 | }, 1118 | "node-addon-api": { 1119 | "version": "2.0.0", 1120 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", 1121 | "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" 1122 | }, 1123 | "node-gyp": { 1124 | "version": "3.8.0", 1125 | "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", 1126 | "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", 1127 | "optional": true, 1128 | "requires": { 1129 | "fstream": "^1.0.0", 1130 | "glob": "^7.0.3", 1131 | "graceful-fs": "^4.1.2", 1132 | "mkdirp": "^0.5.0", 1133 | "nopt": "2 || 3", 1134 | "npmlog": "0 || 1 || 2 || 3 || 4", 1135 | "osenv": "0", 1136 | "request": "^2.87.0", 1137 | "rimraf": "2", 1138 | "semver": "~5.3.0", 1139 | "tar": "^2.0.0", 1140 | "which": "1" 1141 | }, 1142 | "dependencies": { 1143 | "semver": { 1144 | "version": "5.3.0", 1145 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 1146 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 1147 | "optional": true 1148 | } 1149 | } 1150 | }, 1151 | "node-pre-gyp": { 1152 | "version": "0.11.0", 1153 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", 1154 | "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", 1155 | "requires": { 1156 | "detect-libc": "^1.0.2", 1157 | "mkdirp": "^0.5.1", 1158 | "needle": "^2.2.1", 1159 | "nopt": "^4.0.1", 1160 | "npm-packlist": "^1.1.6", 1161 | "npmlog": "^4.0.2", 1162 | "rc": "^1.2.7", 1163 | "rimraf": "^2.6.1", 1164 | "semver": "^5.3.0", 1165 | "tar": "^4" 1166 | }, 1167 | "dependencies": { 1168 | "nopt": { 1169 | "version": "4.0.3", 1170 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", 1171 | "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", 1172 | "requires": { 1173 | "abbrev": "1", 1174 | "osenv": "^0.1.4" 1175 | } 1176 | }, 1177 | "semver": { 1178 | "version": "5.7.1", 1179 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1180 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1181 | }, 1182 | "tar": { 1183 | "version": "4.4.13", 1184 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", 1185 | "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", 1186 | "requires": { 1187 | "chownr": "^1.1.1", 1188 | "fs-minipass": "^1.2.5", 1189 | "minipass": "^2.8.6", 1190 | "minizlib": "^1.2.1", 1191 | "mkdirp": "^0.5.0", 1192 | "safe-buffer": "^5.1.2", 1193 | "yallist": "^3.0.3" 1194 | } 1195 | }, 1196 | "yallist": { 1197 | "version": "3.1.1", 1198 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1199 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1200 | } 1201 | } 1202 | }, 1203 | "nopt": { 1204 | "version": "3.0.6", 1205 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", 1206 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", 1207 | "optional": true, 1208 | "requires": { 1209 | "abbrev": "1" 1210 | } 1211 | }, 1212 | "npm-bundled": { 1213 | "version": "1.1.1", 1214 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 1215 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 1216 | "requires": { 1217 | "npm-normalize-package-bin": "^1.0.1" 1218 | } 1219 | }, 1220 | "npm-normalize-package-bin": { 1221 | "version": "1.0.1", 1222 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 1223 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 1224 | }, 1225 | "npm-packlist": { 1226 | "version": "1.4.8", 1227 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", 1228 | "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", 1229 | "requires": { 1230 | "ignore-walk": "^3.0.1", 1231 | "npm-bundled": "^1.0.1", 1232 | "npm-normalize-package-bin": "^1.0.1" 1233 | } 1234 | }, 1235 | "npmlog": { 1236 | "version": "4.1.2", 1237 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1238 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1239 | "requires": { 1240 | "are-we-there-yet": "~1.1.2", 1241 | "console-control-strings": "~1.1.0", 1242 | "gauge": "~2.7.3", 1243 | "set-blocking": "~2.0.0" 1244 | } 1245 | }, 1246 | "number-is-nan": { 1247 | "version": "1.0.1", 1248 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1249 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1250 | }, 1251 | "oauth-sign": { 1252 | "version": "0.9.0", 1253 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 1254 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 1255 | "optional": true 1256 | }, 1257 | "object-assign": { 1258 | "version": "4.1.1", 1259 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1260 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1261 | }, 1262 | "on-finished": { 1263 | "version": "2.3.0", 1264 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1265 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1266 | "requires": { 1267 | "ee-first": "1.1.1" 1268 | } 1269 | }, 1270 | "once": { 1271 | "version": "1.4.0", 1272 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1273 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1274 | "requires": { 1275 | "wrappy": "1" 1276 | } 1277 | }, 1278 | "os-homedir": { 1279 | "version": "1.0.2", 1280 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1281 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1282 | }, 1283 | "os-tmpdir": { 1284 | "version": "1.0.2", 1285 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1286 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1287 | }, 1288 | "osenv": { 1289 | "version": "0.1.5", 1290 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1291 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1292 | "requires": { 1293 | "os-homedir": "^1.0.0", 1294 | "os-tmpdir": "^1.0.0" 1295 | } 1296 | }, 1297 | "parseurl": { 1298 | "version": "1.3.3", 1299 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1300 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1301 | }, 1302 | "path-is-absolute": { 1303 | "version": "1.0.1", 1304 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1305 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1306 | }, 1307 | "path-to-regexp": { 1308 | "version": "0.1.7", 1309 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1310 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1311 | }, 1312 | "performance-now": { 1313 | "version": "2.1.0", 1314 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1315 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 1316 | "optional": true 1317 | }, 1318 | "process-nextick-args": { 1319 | "version": "2.0.1", 1320 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1321 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1322 | }, 1323 | "proxy-addr": { 1324 | "version": "2.0.6", 1325 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 1326 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 1327 | "requires": { 1328 | "forwarded": "~0.1.2", 1329 | "ipaddr.js": "1.9.1" 1330 | } 1331 | }, 1332 | "psl": { 1333 | "version": "1.8.0", 1334 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 1335 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", 1336 | "optional": true 1337 | }, 1338 | "punycode": { 1339 | "version": "2.1.1", 1340 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1341 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1342 | "optional": true 1343 | }, 1344 | "qs": { 1345 | "version": "6.5.2", 1346 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 1347 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", 1348 | "optional": true 1349 | }, 1350 | "range-parser": { 1351 | "version": "1.2.1", 1352 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1353 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1354 | }, 1355 | "raw-body": { 1356 | "version": "2.4.0", 1357 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1358 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1359 | "requires": { 1360 | "bytes": "3.1.0", 1361 | "http-errors": "1.7.2", 1362 | "iconv-lite": "0.4.24", 1363 | "unpipe": "1.0.0" 1364 | } 1365 | }, 1366 | "rc": { 1367 | "version": "1.2.8", 1368 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1369 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1370 | "requires": { 1371 | "deep-extend": "^0.6.0", 1372 | "ini": "~1.3.0", 1373 | "minimist": "^1.2.0", 1374 | "strip-json-comments": "~2.0.1" 1375 | } 1376 | }, 1377 | "readable-stream": { 1378 | "version": "2.3.7", 1379 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1380 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1381 | "requires": { 1382 | "core-util-is": "~1.0.0", 1383 | "inherits": "~2.0.3", 1384 | "isarray": "~1.0.0", 1385 | "process-nextick-args": "~2.0.0", 1386 | "safe-buffer": "~5.1.1", 1387 | "string_decoder": "~1.1.1", 1388 | "util-deprecate": "~1.0.1" 1389 | } 1390 | }, 1391 | "request": { 1392 | "version": "2.88.2", 1393 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 1394 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 1395 | "optional": true, 1396 | "requires": { 1397 | "aws-sign2": "~0.7.0", 1398 | "aws4": "^1.8.0", 1399 | "caseless": "~0.12.0", 1400 | "combined-stream": "~1.0.6", 1401 | "extend": "~3.0.2", 1402 | "forever-agent": "~0.6.1", 1403 | "form-data": "~2.3.2", 1404 | "har-validator": "~5.1.3", 1405 | "http-signature": "~1.2.0", 1406 | "is-typedarray": "~1.0.0", 1407 | "isstream": "~0.1.2", 1408 | "json-stringify-safe": "~5.0.1", 1409 | "mime-types": "~2.1.19", 1410 | "oauth-sign": "~0.9.0", 1411 | "performance-now": "^2.1.0", 1412 | "qs": "~6.5.2", 1413 | "safe-buffer": "^5.1.2", 1414 | "tough-cookie": "~2.5.0", 1415 | "tunnel-agent": "^0.6.0", 1416 | "uuid": "^3.3.2" 1417 | }, 1418 | "dependencies": { 1419 | "uuid": { 1420 | "version": "3.4.0", 1421 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1422 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", 1423 | "optional": true 1424 | } 1425 | } 1426 | }, 1427 | "retry-as-promised": { 1428 | "version": "3.2.0", 1429 | "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", 1430 | "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", 1431 | "requires": { 1432 | "any-promise": "^1.3.0" 1433 | } 1434 | }, 1435 | "rimraf": { 1436 | "version": "2.7.1", 1437 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1438 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1439 | "requires": { 1440 | "glob": "^7.1.3" 1441 | } 1442 | }, 1443 | "safe-buffer": { 1444 | "version": "5.1.2", 1445 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1446 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1447 | }, 1448 | "safer-buffer": { 1449 | "version": "2.1.2", 1450 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1451 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1452 | }, 1453 | "sax": { 1454 | "version": "1.2.4", 1455 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1456 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1457 | }, 1458 | "semver": { 1459 | "version": "7.3.4", 1460 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", 1461 | "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", 1462 | "requires": { 1463 | "lru-cache": "^6.0.0" 1464 | } 1465 | }, 1466 | "send": { 1467 | "version": "0.17.1", 1468 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1469 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1470 | "requires": { 1471 | "debug": "2.6.9", 1472 | "depd": "~1.1.2", 1473 | "destroy": "~1.0.4", 1474 | "encodeurl": "~1.0.2", 1475 | "escape-html": "~1.0.3", 1476 | "etag": "~1.8.1", 1477 | "fresh": "0.5.2", 1478 | "http-errors": "~1.7.2", 1479 | "mime": "1.6.0", 1480 | "ms": "2.1.1", 1481 | "on-finished": "~2.3.0", 1482 | "range-parser": "~1.2.1", 1483 | "statuses": "~1.5.0" 1484 | }, 1485 | "dependencies": { 1486 | "debug": { 1487 | "version": "2.6.9", 1488 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1489 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1490 | "requires": { 1491 | "ms": "2.0.0" 1492 | }, 1493 | "dependencies": { 1494 | "ms": { 1495 | "version": "2.0.0", 1496 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1497 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1498 | } 1499 | } 1500 | }, 1501 | "ms": { 1502 | "version": "2.1.1", 1503 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1504 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1505 | } 1506 | } 1507 | }, 1508 | "sequelize": { 1509 | "version": "6.3.5", 1510 | "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", 1511 | "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", 1512 | "requires": { 1513 | "debug": "^4.1.1", 1514 | "dottie": "^2.0.0", 1515 | "inflection": "1.12.0", 1516 | "lodash": "^4.17.15", 1517 | "moment": "^2.26.0", 1518 | "moment-timezone": "^0.5.31", 1519 | "retry-as-promised": "^3.2.0", 1520 | "semver": "^7.3.2", 1521 | "sequelize-pool": "^6.0.0", 1522 | "toposort-class": "^1.0.1", 1523 | "uuid": "^8.1.0", 1524 | "validator": "^10.11.0", 1525 | "wkx": "^0.5.0" 1526 | } 1527 | }, 1528 | "sequelize-pool": { 1529 | "version": "6.1.0", 1530 | "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", 1531 | "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==" 1532 | }, 1533 | "serve-static": { 1534 | "version": "1.14.1", 1535 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1536 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1537 | "requires": { 1538 | "encodeurl": "~1.0.2", 1539 | "escape-html": "~1.0.3", 1540 | "parseurl": "~1.3.3", 1541 | "send": "0.17.1" 1542 | } 1543 | }, 1544 | "set-blocking": { 1545 | "version": "2.0.0", 1546 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1547 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1548 | }, 1549 | "setprototypeof": { 1550 | "version": "1.1.1", 1551 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1552 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1553 | }, 1554 | "signal-exit": { 1555 | "version": "3.0.3", 1556 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1557 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 1558 | }, 1559 | "source-map": { 1560 | "version": "0.6.1", 1561 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1562 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 1563 | }, 1564 | "source-map-support": { 1565 | "version": "0.5.19", 1566 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 1567 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 1568 | "dev": true, 1569 | "requires": { 1570 | "buffer-from": "^1.0.0", 1571 | "source-map": "^0.6.0" 1572 | } 1573 | }, 1574 | "sqlite3": { 1575 | "version": "5.0.0", 1576 | "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz", 1577 | "integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==", 1578 | "requires": { 1579 | "node-addon-api": "2.0.0", 1580 | "node-gyp": "3.x", 1581 | "node-pre-gyp": "^0.11.0" 1582 | } 1583 | }, 1584 | "sshpk": { 1585 | "version": "1.16.1", 1586 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 1587 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 1588 | "optional": true, 1589 | "requires": { 1590 | "asn1": "~0.2.3", 1591 | "assert-plus": "^1.0.0", 1592 | "bcrypt-pbkdf": "^1.0.0", 1593 | "dashdash": "^1.12.0", 1594 | "ecc-jsbn": "~0.1.1", 1595 | "getpass": "^0.1.1", 1596 | "jsbn": "~0.1.0", 1597 | "safer-buffer": "^2.0.2", 1598 | "tweetnacl": "~0.14.0" 1599 | } 1600 | }, 1601 | "statuses": { 1602 | "version": "1.5.0", 1603 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1604 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1605 | }, 1606 | "string-width": { 1607 | "version": "1.0.2", 1608 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1609 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1610 | "requires": { 1611 | "code-point-at": "^1.0.0", 1612 | "is-fullwidth-code-point": "^1.0.0", 1613 | "strip-ansi": "^3.0.0" 1614 | } 1615 | }, 1616 | "string_decoder": { 1617 | "version": "1.1.1", 1618 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1619 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1620 | "requires": { 1621 | "safe-buffer": "~5.1.0" 1622 | } 1623 | }, 1624 | "strip-ansi": { 1625 | "version": "3.0.1", 1626 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1627 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1628 | "requires": { 1629 | "ansi-regex": "^2.0.0" 1630 | } 1631 | }, 1632 | "strip-json-comments": { 1633 | "version": "2.0.1", 1634 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1635 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1636 | }, 1637 | "tar": { 1638 | "version": "2.2.2", 1639 | "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", 1640 | "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", 1641 | "optional": true, 1642 | "requires": { 1643 | "block-stream": "*", 1644 | "fstream": "^1.0.12", 1645 | "inherits": "2" 1646 | } 1647 | }, 1648 | "toidentifier": { 1649 | "version": "1.0.0", 1650 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1651 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1652 | }, 1653 | "toposort-class": { 1654 | "version": "1.0.1", 1655 | "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", 1656 | "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" 1657 | }, 1658 | "tough-cookie": { 1659 | "version": "2.5.0", 1660 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1661 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1662 | "optional": true, 1663 | "requires": { 1664 | "psl": "^1.1.28", 1665 | "punycode": "^2.1.1" 1666 | } 1667 | }, 1668 | "ts-node": { 1669 | "version": "9.1.1", 1670 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", 1671 | "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", 1672 | "dev": true, 1673 | "requires": { 1674 | "arg": "^4.1.0", 1675 | "create-require": "^1.1.0", 1676 | "diff": "^4.0.1", 1677 | "make-error": "^1.1.1", 1678 | "source-map-support": "^0.5.17", 1679 | "yn": "3.1.1" 1680 | } 1681 | }, 1682 | "tunnel-agent": { 1683 | "version": "0.6.0", 1684 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1685 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1686 | "optional": true, 1687 | "requires": { 1688 | "safe-buffer": "^5.0.1" 1689 | } 1690 | }, 1691 | "tweetnacl": { 1692 | "version": "0.14.5", 1693 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1694 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1695 | "optional": true 1696 | }, 1697 | "type-is": { 1698 | "version": "1.6.18", 1699 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1700 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1701 | "requires": { 1702 | "media-typer": "0.3.0", 1703 | "mime-types": "~2.1.24" 1704 | } 1705 | }, 1706 | "uglify-js": { 1707 | "version": "3.12.4", 1708 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz", 1709 | "integrity": "sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==", 1710 | "optional": true 1711 | }, 1712 | "unpipe": { 1713 | "version": "1.0.0", 1714 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1715 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1716 | }, 1717 | "uri-js": { 1718 | "version": "4.4.0", 1719 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", 1720 | "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", 1721 | "optional": true, 1722 | "requires": { 1723 | "punycode": "^2.1.0" 1724 | } 1725 | }, 1726 | "util-deprecate": { 1727 | "version": "1.0.2", 1728 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1729 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1730 | }, 1731 | "utils-merge": { 1732 | "version": "1.0.1", 1733 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1734 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1735 | }, 1736 | "uuid": { 1737 | "version": "8.3.1", 1738 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", 1739 | "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" 1740 | }, 1741 | "validator": { 1742 | "version": "10.11.0", 1743 | "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", 1744 | "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" 1745 | }, 1746 | "vary": { 1747 | "version": "1.1.2", 1748 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1749 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1750 | }, 1751 | "verror": { 1752 | "version": "1.10.0", 1753 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1754 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1755 | "optional": true, 1756 | "requires": { 1757 | "assert-plus": "^1.0.0", 1758 | "core-util-is": "1.0.2", 1759 | "extsprintf": "^1.2.0" 1760 | } 1761 | }, 1762 | "which": { 1763 | "version": "1.3.1", 1764 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1765 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1766 | "optional": true, 1767 | "requires": { 1768 | "isexe": "^2.0.0" 1769 | } 1770 | }, 1771 | "wide-align": { 1772 | "version": "1.1.3", 1773 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1774 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1775 | "requires": { 1776 | "string-width": "^1.0.2 || 2" 1777 | } 1778 | }, 1779 | "wkx": { 1780 | "version": "0.5.0", 1781 | "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", 1782 | "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", 1783 | "requires": { 1784 | "@types/node": "*" 1785 | } 1786 | }, 1787 | "wordwrap": { 1788 | "version": "1.0.0", 1789 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1790 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" 1791 | }, 1792 | "wrappy": { 1793 | "version": "1.0.2", 1794 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1795 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1796 | }, 1797 | "xstate": { 1798 | "version": "4.15.3", 1799 | "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.15.3.tgz", 1800 | "integrity": "sha512-nf4zzLNs5W57stMZib9UG9PA5ywu89INsaXBMZf7iQxkYD9apbIOQcK8nu/iVZEDOVE+vR8GQnTaOg/8iDSK5Q==" 1801 | }, 1802 | "yallist": { 1803 | "version": "4.0.0", 1804 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1805 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1806 | }, 1807 | "yn": { 1808 | "version": "3.1.1", 1809 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1810 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1811 | "dev": true 1812 | } 1813 | } 1814 | } 1815 | -------------------------------------------------------------------------------- /examples/sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "body-parser": "^1.19.0", 8 | "express": "^4.17.1", 9 | "express-jwt": "^6.0.0", 10 | "microflow": "^3.0.0-alpha.2", 11 | "sequelize": "^6.3.5", 12 | "sqlite3": "^5.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/body-parser": "^1.19.0", 16 | "@types/express": "^4.17.9", 17 | "@types/express-jwt": "0.0.42", 18 | "@types/node": "^14.14.11", 19 | "ts-node": "^9.1.1" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "author": "", 25 | "license": "ISC" 26 | } 27 | -------------------------------------------------------------------------------- /img/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krn0x2/microflow/769d642ae86755b5d6cb564a81219fe4c57997ec/img/workflow.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testTimeout: 20000 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microflow", 3 | "version": "3.0.0-alpha.8", 4 | "description": "Finite state machine based HTTP microservice orchestration", 5 | "keywords": [ 6 | "workflow", 7 | "state machine", 8 | "orchestration", 9 | "finite state machine", 10 | "step function" 11 | ], 12 | "files": [ 13 | "lib/**/*.js", 14 | "lib/**/*.d.ts" 15 | ], 16 | "main": "lib/microflow.js", 17 | "dependencies": { 18 | "axios": "^0.21.0", 19 | "bluebird": "^3.7.2", 20 | "handlebars": "^4.7.6", 21 | "jsonpath-plus": "^4.0.0", 22 | "jsonwebtoken": "^8.5.1", 23 | "lodash": "^4.17.20", 24 | "nanoid": "^3.1.20", 25 | "xstate": "^4.14.0" 26 | }, 27 | "devDependencies": { 28 | "@types/bluebird": "^3.5.33", 29 | "@types/jest": "^26.0.19", 30 | "@types/jsonwebtoken": "^8.5.0", 31 | "@types/lodash": "^4.14.165", 32 | "@typescript-eslint/eslint-plugin": "^4.8.0", 33 | "@typescript-eslint/parser": "^4.8.0", 34 | "eslint": "^7.13.0", 35 | "jest": "^26.6.3", 36 | "ts-jest": "^26.4.4", 37 | "typescript": "^4.0.5" 38 | }, 39 | "scripts": { 40 | "build": "tsc", 41 | "test": "jest" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/krn0x2/microflow.git" 46 | }, 47 | "author": { 48 | "name": "Karan Chhabra" 49 | }, 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/krn0x2/microflow/issues" 53 | }, 54 | "homepage": "https://github.com/krn0x2/microflow#readme" 55 | } 56 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const MICROFLOW = { 2 | CONCURRENCY: 20, 3 | STATES: { 4 | TASK: 'task', 5 | TASK_SYNC: 'taskSync', 6 | TASK_MAP_SYNC: 'taskMapSync', 7 | ATOMIC: 'atomic', 8 | FINAL: 'final' 9 | }, 10 | ERRORS: { 11 | WORKFLOW: { 12 | FAILED_TO_FETCH: 'FAILED_TO_FETCH' 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/controllers/execution.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { Promise as BluebirdPromise } from 'bluebird'; 3 | import { State } from 'xstate'; 4 | import { WorkflowInterpreter } from '../interpreter'; 5 | import { IExecution, WorkflowEvent } from '../types'; 6 | import { getMachine } from '../utils/xstate'; 7 | import { EntityController } from '.'; 8 | 9 | export class Execution extends EntityController { 10 | async send(event: WorkflowEvent): Promise { 11 | const { definition, currentJson } = await this.data(); 12 | const fetchMachine = getMachine( 13 | definition, 14 | this.jwt.secretOrPublicKey, 15 | this.jwt.sign 16 | ); 17 | const previousState = State.create(currentJson) as State< 18 | any, 19 | WorkflowEvent 20 | >; 21 | const resolvedState = fetchMachine.resolveState(previousState); 22 | const { nextEvents } = resolvedState; 23 | const { type } = event; 24 | if (resolvedState.done || !_.includes(nextEvents, type)) return this; 25 | 26 | const service = new WorkflowInterpreter(fetchMachine).start(resolvedState); 27 | return new BluebirdPromise((res) => { 28 | service 29 | .onTransition(async (state) => { 30 | if (state.changed !== undefined && _.isEmpty(state.children)) { 31 | await this.update({ 32 | currentJson: state, 33 | state: state.value, 34 | output: state.event.data, 35 | completed: state.done 36 | }); 37 | res(this); 38 | } 39 | }) 40 | .send(event); 41 | }) 42 | .timeout(this.timeout) 43 | .catch(() => { 44 | return this; 45 | }) 46 | .finally(() => service.stop()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import { ICrudable, IModel } from '../crudable'; 2 | import { IJwt, IMicroflowStorage } from '../types'; 3 | 4 | export class EntityController { 5 | constructor( 6 | private model: T, 7 | private store: ICrudable, 8 | protected storage: IMicroflowStorage, 9 | protected jwt: IJwt, 10 | protected timeout: number = 10000 11 | ) { 12 | } 13 | 14 | async data(): Promise { 15 | return this.store.read(this.model.id); 16 | } 17 | 18 | async update(data: Partial>): Promise { 19 | return this.store.update(this.model.id, data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/controllers/task.ts: -------------------------------------------------------------------------------- 1 | import { EntityController } from '.'; 2 | import { ITask } from '../types'; 3 | 4 | export class Task extends EntityController {} 5 | -------------------------------------------------------------------------------- /src/controllers/workflow.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IExecution, 3 | IJwt, 4 | IMicroflowStorage, 5 | ITask, 6 | IWorkflow 7 | } from '../types'; 8 | import { Execution } from './execution'; 9 | import { transformConfig } from '../utils/convert'; 10 | import { getMachine } from '../utils/xstate'; 11 | import { ICrudable } from '../crudable'; 12 | import { EntityController } from '.'; 13 | import { MicroflowCore } from '../core'; 14 | 15 | export class Workflow extends EntityController { 16 | private getTask: (id: string) => Promise; 17 | private execution: MicroflowCore; 18 | 19 | constructor( 20 | model: IWorkflow, 21 | store: ICrudable, 22 | storage: IMicroflowStorage, 23 | jwt: IJwt, 24 | timeout: number, 25 | ) { 26 | super(model, store, storage, jwt); 27 | this.getTask = storage.task.read.bind(storage.task); 28 | this.execution = new MicroflowCore( 29 | Execution, 30 | storage.execution, 31 | storage, 32 | this.jwt, 33 | timeout, 34 | ); 35 | this.execution.create = this.execution.create.bind(this.execution); 36 | this.execution.update = this.execution.update.bind(this.execution); 37 | } 38 | 39 | async start( 40 | data: Record = {}, 41 | name?: string 42 | ): Promise { 43 | const { config } = await this.data(); 44 | const definition = await transformConfig(config, this.getTask); 45 | const workflowMachine = getMachine( 46 | definition, 47 | this.jwt.secretOrPublicKey, 48 | this.jwt.sign 49 | ); 50 | const { initialState } = workflowMachine; 51 | const execution = await this.execution.create({ 52 | name, 53 | config, 54 | definition, 55 | state: initialState.value, 56 | completed: initialState.done 57 | }); 58 | const { id: executionId } = await execution.data(); 59 | const { initialState: currentJson } = workflowMachine.withContext({ 60 | wfid: executionId, 61 | name 62 | }); 63 | await this.execution.update(executionId, { 64 | currentJson 65 | }); 66 | return execution.send({ type: 'data', data }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import { ICrudable, IModel } from './crudable'; 2 | import { IJwt, IMicroflowStorage } from './types'; 3 | 4 | interface EntityConstructor { 5 | new (model: T, store: ICrudable, storage: IMicroflowStorage, jwt: IJwt, timeout: number): U; 6 | } 7 | 8 | export class MicroflowCore { 9 | constructor( 10 | private ctor: EntityConstructor, 11 | private store: ICrudable, 12 | private storage?: IMicroflowStorage, 13 | private jwt?: IJwt, 14 | private timeout?: number, 15 | ) { 16 | } 17 | 18 | async create(data: Omit & { id?: T['id'] }): Promise { 19 | const model = await this.store.create(data); 20 | return new this.ctor(model, this.store, this.storage, this.jwt, this.timeout); 21 | } 22 | 23 | async read(id: T['id']): Promise { 24 | const model = await this.store.read(id); 25 | return new this.ctor(model, this.store, this.storage, this.jwt, this.timeout); 26 | } 27 | 28 | async update(id: T['id'], data: Partial>): Promise { 29 | const model = await this.store.update(id, data); 30 | return new this.ctor(model, this.store, this.storage, this.jwt, this.timeout); 31 | } 32 | 33 | async delete(id: T['id']): Promise { 34 | return this.store.delete(id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/crudable.ts: -------------------------------------------------------------------------------- 1 | export interface IModel { 2 | id: string; 3 | } 4 | 5 | export interface ICrudable { 6 | create(data: Omit & { id?: T['id'] }): Promise; 7 | read(id: T['id']): Promise; 8 | update(id: T['id'], data: Partial>): Promise; 9 | delete(id: T['id']): Promise; 10 | } 11 | 12 | export class Crudable implements ICrudable { 13 | create(data: Omit & { id?: T['id'] }): Promise { 14 | throw new Error('Method not implemented.'); 15 | } 16 | read(id: T['id']): Promise { 17 | throw new Error('Method not implemented.'); 18 | } 19 | update(id: T['id'], data: Partial>): Promise { 20 | throw new Error('Method not implemented.'); 21 | } 22 | delete(id: T['id']): Promise { 23 | throw new Error('Method not implemented.'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/interpreter/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Interpreter, 3 | StateSchema, 4 | EventData, 5 | Typestate, 6 | State, 7 | InterpreterOptions, 8 | StateMachine, 9 | SCXML 10 | } from 'xstate'; 11 | import _ from 'lodash'; 12 | import { transform, setOnPath } from '../utils'; 13 | import { WorkflowEvent } from '../types'; 14 | 15 | export class WorkflowInterpreter extends Interpreter< 16 | any, 17 | StateSchema, 18 | WorkflowEvent, 19 | Typestate 20 | > { 21 | oldSend: any; 22 | constructor( 23 | machine: StateMachine>, 24 | options?: Partial 25 | ) { 26 | super(machine, options); 27 | this.oldSend = this.send.bind(this); 28 | this.send = ( 29 | event: WorkflowEvent | SCXML.Event, 30 | payload?: EventData 31 | ): State> => { 32 | const isSCXML = _.get(event, '$$type') === 'scxml'; 33 | const data = isSCXML 34 | ? _.get(event, 'data.data', {}) 35 | : _.get(event, 'data', {}); 36 | const lastEventData = _.get(this.state.event, 'data', {}); 37 | const isMapNode = _.get( 38 | this.state, 39 | 'activities.task.activity.src.isMap', 40 | false 41 | ); 42 | const { context: ctx } = this.state; 43 | const { transitions } = this.machine.transition(this.state, event); 44 | const transition = _.head(transitions); 45 | const resultSelector = _.get(transition, 'resultSelector'); 46 | const resultPath = _.get(transition, 'resultPath'); 47 | const resultSelected = isMapNode 48 | ? data.map((value, index) => 49 | transform( 50 | resultSelector, 51 | { 52 | _: data, 53 | _task: { 54 | executionId: ctx.wfid, 55 | name: ctx.name, 56 | item: { value, index } 57 | }, 58 | _env: process.env 59 | }, 60 | value 61 | ) 62 | ) 63 | : transform(resultSelector, { 64 | _: data, 65 | _env: process.env 66 | }); 67 | const result = setOnPath(lastEventData, resultPath, resultSelected); 68 | _.set(event, isSCXML ? 'data.data' : 'data', result); 69 | return this.oldSend(event, payload); 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/localCrud.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | import { ICrudable, IModel } from './crudable'; 3 | 4 | export class LocalCrud implements ICrudable { 5 | store: Map; 6 | 7 | constructor(map: Map) { 8 | this.store = map; 9 | } 10 | 11 | async create(data: Omit & { id?: T['id'] }): Promise { 12 | const id = data.id || nanoid(); 13 | this.store.set(id, { id, ...data } as T); 14 | return this.store.get(id); 15 | } 16 | 17 | async read(id: T['id']): Promise { 18 | if (!this.store.has(id)) throw new Error(`Item with id = ${id} not found`); 19 | return this.store.get(id); 20 | } 21 | 22 | async update(id: T['id'], data: Partial>): Promise { 23 | if (!this.store.has(id)) throw new Error(`Item with id = ${id} not found`); 24 | this.store.set(id, { ...this.store.get(id), ...data }); 25 | return this.store.get(id); 26 | } 27 | 28 | async delete(id: T['id']): Promise { 29 | if (!this.store.has(id)) throw new Error(`Item with id = ${id} not found`); 30 | this.store.delete(id); 31 | return id; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/microflow.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { DefaultStorage } from './storage'; 3 | import { Task } from './controllers/task'; 4 | import { Workflow } from './controllers/workflow'; 5 | import { Execution } from './controllers/execution'; 6 | import { 7 | IMicroflowStorage, 8 | IMicroflowConfig, 9 | TaskTokenClaims, 10 | IWorkflow, 11 | ITask, 12 | IExecution 13 | } from './types'; 14 | import { MicroflowCore } from './core'; 15 | 16 | export class Microflow { 17 | private secret: jwt.Secret; 18 | private signOptions: jwt.SignOptions; 19 | private verifyOptions: jwt.VerifyOptions; 20 | 21 | public storage: IMicroflowStorage; 22 | 23 | public workflow: MicroflowCore; 24 | public task: MicroflowCore; 25 | public execution: MicroflowCore; 26 | 27 | constructor(config: IMicroflowConfig) { 28 | const { storage, jwt, timeout } = config; 29 | const { secretOrPublicKey, sign, verify } = jwt; 30 | this.secret = secretOrPublicKey; 31 | this.signOptions = sign; 32 | this.verifyOptions = verify; 33 | if (!storage) { 34 | this.storage = new DefaultStorage(); 35 | } else { 36 | this.storage = storage; 37 | } 38 | this.workflow = new MicroflowCore( 39 | Workflow, 40 | this.storage.workflow, 41 | this.storage, 42 | { secretOrPublicKey: this.secret, sign: this.signOptions }, 43 | timeout 44 | ); 45 | this.task = new MicroflowCore(Task, this.storage.task); 46 | this.execution = new MicroflowCore( 47 | Execution, 48 | this.storage.execution, 49 | this.storage, 50 | { secretOrPublicKey: this.secret, sign: this.signOptions }, 51 | timeout, 52 | ); 53 | } 54 | 55 | private claimsFromTaskToken(token): TaskTokenClaims { 56 | return jwt.verify( 57 | token, 58 | this.secret, 59 | this.verifyOptions 60 | ) as TaskTokenClaims; 61 | } 62 | 63 | async sendTaskSuccess( 64 | token: string, 65 | data: Record 66 | ): Promise { 67 | try { 68 | const { taskEventSuffix, workflowInstanceId } = this.claimsFromTaskToken( 69 | token 70 | ); 71 | const execution = await this.execution.read(workflowInstanceId); 72 | return execution.send({ 73 | type: `success-${taskEventSuffix}`, 74 | data 75 | }); 76 | } catch (ex) { 77 | throw { 78 | message: 'Task token invalid' 79 | }; 80 | } 81 | } 82 | 83 | async sendTaskFailure( 84 | token: string, 85 | data: Record 86 | ): Promise { 87 | try { 88 | const { taskEventSuffix, workflowInstanceId } = this.claimsFromTaskToken( 89 | token 90 | ); 91 | const execution = await this.execution.read(workflowInstanceId); 92 | return execution.send({ 93 | type: `failure-${taskEventSuffix}`, 94 | data 95 | }); 96 | } catch (ex) { 97 | throw { 98 | message: 'Task token invalid' 99 | }; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/storage/index.ts: -------------------------------------------------------------------------------- 1 | import { ICrudable } from '../crudable'; 2 | import { LocalCrud } from '../localCrud'; 3 | import { ITask, IWorkflow, IExecution, IMicroflowStorage } from '../types'; 4 | 5 | export class DefaultStorage implements IMicroflowStorage { 6 | private memory = { 7 | workflows: new Map(), 8 | tasks: new Map(), 9 | executions: new Map() 10 | }; 11 | workflow: ICrudable; 12 | task: ICrudable; 13 | execution: ICrudable; 14 | 15 | constructor() { 16 | this.workflow = new LocalCrud(this.memory.workflows); 17 | this.task = new LocalCrud(this.memory.tasks); 18 | this.execution = new LocalCrud(this.memory.executions); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { 3 | EventObject, 4 | MachineConfig, 5 | State, 6 | StateSchema, 7 | StateValue, 8 | Typestate 9 | } from 'xstate'; 10 | import { ICrudable, IModel } from './crudable'; 11 | 12 | export class Error { 13 | code: number; 14 | message: string; 15 | constructor(code: number, message: string) { 16 | this.code = code; 17 | this.message = message; 18 | } 19 | } 20 | 21 | export type SingleOrArray = T[] | T; 22 | 23 | export type ITransform = 24 | | SingleOrArray 25 | | SingleOrArray 26 | | SingleOrArray>; 27 | 28 | export interface IMicroflowConfig { 29 | storage?: IMicroflowStorage; 30 | timeout?: number; 31 | jwt: IJwt; 32 | } 33 | 34 | export interface IJwt { 35 | secretOrPublicKey: jwt.Secret; 36 | sign?: jwt.SignOptions; 37 | verify?: jwt.VerifyOptions; 38 | } 39 | 40 | export interface IWorkflow extends IModel { 41 | config: MicroflowDefinition; 42 | } 43 | 44 | export interface ITask extends IModel { 45 | type: 'http' | 'other'; 46 | config: any; 47 | } 48 | 49 | export interface IExecution extends IModel { 50 | name?: string; 51 | config: MicroflowDefinition; 52 | definition: MachineConfig; 53 | currentJson?: State, Typestate>; 54 | state: StateValue; 55 | output?: Record; 56 | completed: boolean; 57 | } 58 | 59 | export interface WorkflowInput { 60 | id: string; 61 | config: MicroflowDefinition; 62 | } 63 | 64 | export interface TaskInput { 65 | id: string; 66 | type: 'http' | 'other'; 67 | config: any; 68 | } 69 | 70 | export interface WorkflowInstanceInput { 71 | definition: any; 72 | currentJson: any; 73 | } 74 | 75 | export interface WorkflowEvent extends EventObject { 76 | data?: Record; 77 | } 78 | 79 | export interface TaskTokenClaims { 80 | workflowInstanceId: string; 81 | taskEventSuffix: string; 82 | } 83 | 84 | export type SendEventError = { 85 | message: string; 86 | }; 87 | 88 | export type SendEventSuccess = { 89 | currentState: StateValue; 90 | completed: boolean; 91 | nextEvents: string[]; 92 | }; 93 | 94 | export type StartWorkflowResponse = { 95 | id: string; 96 | }; 97 | 98 | export type SendEventResponse = SendEventSuccess; 99 | 100 | export type MicroflowStateTypes = 101 | | 'task' 102 | | 'taskSync' 103 | | 'taskMapSync' 104 | | 'pass' 105 | | 'atomic' 106 | | 'final'; 107 | 108 | export interface TransitionConfig { 109 | target: string; 110 | meta?: Record; 111 | resultSelector?: Record; 112 | resultPath?: string; 113 | } 114 | 115 | export interface StateNodeConfig { 116 | type: MicroflowStateTypes; 117 | meta?: Record; 118 | on?: Record; 119 | } 120 | 121 | export interface AtomicNodeConfig extends StateNodeConfig { 122 | type: 'atomic'; 123 | } 124 | 125 | export interface FinalNodeConfig { 126 | type: 'final'; 127 | meta?: Record; 128 | } 129 | 130 | export interface PassNodeConfig extends StateNodeConfig { 131 | type: 'pass'; 132 | onDone?: TransitionConfig; 133 | onError?: TransitionConfig; 134 | } 135 | 136 | export interface TaskNodeConfig extends StateNodeConfig { 137 | type: 'task'; 138 | taskId: string; 139 | parameters?: Record; 140 | onDone?: TransitionConfig; 141 | onError?: TransitionConfig; 142 | } 143 | 144 | export interface TaskNodeSyncConfig extends StateNodeConfig { 145 | type: 'taskSync'; 146 | taskId: string; 147 | parameters?: Record; 148 | onDone?: TransitionConfig; 149 | onError?: TransitionConfig; 150 | } 151 | 152 | export interface TaskMapNodeSyncConfig extends StateNodeConfig { 153 | type: 'taskMapSync'; 154 | taskId: string; 155 | itemsPath: string; 156 | parameters?: Record; 157 | onDone?: TransitionConfig; 158 | onError?: TransitionConfig; 159 | } 160 | 161 | export type TMicroflowNode = 162 | | TaskNodeConfig 163 | | TaskNodeSyncConfig 164 | | TaskMapNodeSyncConfig 165 | | PassNodeConfig 166 | | AtomicNodeConfig 167 | | FinalNodeConfig; 168 | 169 | export interface MicroflowDefinition { 170 | initial: string; 171 | meta?: Record; 172 | states: Record; 173 | context?: Record; 174 | } 175 | 176 | export interface IMicroflowStorage { 177 | workflow: ICrudable; 178 | task: ICrudable; 179 | execution: ICrudable; 180 | } 181 | -------------------------------------------------------------------------------- /src/utils/convert.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { map } from 'bluebird'; 3 | import { MachineConfig, StateNodeConfig as XStateNodeConfig } from 'xstate'; 4 | 5 | import { 6 | AtomicNodeConfig, 7 | FinalNodeConfig, 8 | ITask, 9 | MicroflowDefinition, 10 | TaskNodeConfig, 11 | WorkflowEvent, 12 | TransitionConfig, 13 | TaskNodeSyncConfig, 14 | TMicroflowNode, 15 | TaskMapNodeSyncConfig, 16 | PassNodeConfig 17 | } from '../types'; 18 | import { MICROFLOW } from '../constants'; 19 | import { nanoid } from 'nanoid'; 20 | 21 | export async function transformConfig( 22 | config: MicroflowDefinition, 23 | getTask: (id: string) => Promise 24 | ): Promise> { 25 | const states = _.get(config, 'states', {}); 26 | const uniqueTaskIds = _.chain(states) 27 | .mapValues() 28 | .filter((s) => { 29 | const type = _.get(s, 'type'); 30 | return ( 31 | type === MICROFLOW.STATES.TASK || 32 | type === MICROFLOW.STATES.TASK_SYNC || 33 | type === MICROFLOW.STATES.TASK_MAP_SYNC 34 | ); 35 | }) 36 | .map('taskId') 37 | .uniq() 38 | .value(); 39 | const tasks = await map(uniqueTaskIds, getTask, { 40 | concurrency: MICROFLOW.CONCURRENCY 41 | }); 42 | const taskMap = _.keyBy(tasks, 'id'); 43 | return microflowToXstateConfig(config, taskMap); 44 | } 45 | 46 | export function microflowToXstateConfig( 47 | microflowConfig: MicroflowDefinition, 48 | taskDictionary: _.Dictionary 49 | ): MachineConfig { 50 | const { states } = microflowConfig; 51 | const newStates = _.mapValues(states, (s: TMicroflowNode, name) => { 52 | if (s.type === 'task') 53 | return microflowTaskToXstateNode(s, taskDictionary[s.taskId], name); 54 | else if (s.type === 'taskSync') 55 | return microflowTaskSyncToXstateNode(s, taskDictionary[s.taskId]); 56 | else if (s.type === 'taskMapSync') 57 | return microflowTaskMapSyncToXstateNode(s, taskDictionary[s.taskId]); 58 | else if (s.type === 'atomic') return microflowAtomicToXstateNode(s); 59 | else if (s.type === 'pass') return microflowPassToXstateNode(s); 60 | else return microflowFinalToXstateNode(s); 61 | }) as Record>; 62 | 63 | const initialNodeName = nanoid(); 64 | return { 65 | id: 'main', 66 | initial: initialNodeName, 67 | meta: microflowConfig.meta, 68 | states: { 69 | [initialNodeName]: { 70 | on: { 71 | data: { 72 | target: microflowConfig.initial, 73 | resultPath: '$' 74 | } as TransitionConfig 75 | } 76 | }, 77 | ...newStates 78 | } 79 | }; 80 | } 81 | 82 | export function microflowTaskToXstateNode( 83 | s: TaskNodeConfig, 84 | task: ITask, 85 | name?: string 86 | ): XStateNodeConfig { 87 | // const taskEventSuffix = nanoid(); 88 | const taskEventSuffix = name; 89 | return { 90 | type: 'compound', 91 | initial: 'starting', 92 | meta: s.meta, 93 | states: { 94 | starting: { 95 | invoke: { 96 | src: { 97 | type: s.type, 98 | taskId: s.taskId, 99 | task, 100 | parameters: s.parameters, 101 | taskEventSuffix 102 | }, 103 | onDone: { 104 | target: 'started' 105 | }, 106 | onError: { 107 | target: 'failed_to_start', 108 | resultPath: `$.errors.${taskEventSuffix}` 109 | } as TransitionConfig 110 | } 111 | }, 112 | started: { 113 | on: { 114 | [`success-${taskEventSuffix}`]: { 115 | ...s.onDone, 116 | target: '#main.' + s.onDone.target 117 | }, 118 | [`failure-${taskEventSuffix}`]: { 119 | ...s.onError, 120 | target: '#main.' + s.onError.target 121 | } 122 | } 123 | }, 124 | failed_to_start: {} 125 | }, 126 | on: s.on 127 | }; 128 | } 129 | 130 | export function microflowTaskSyncToXstateNode( 131 | s: TaskNodeSyncConfig, 132 | task: ITask 133 | ): XStateNodeConfig { 134 | return { 135 | type: 'atomic', 136 | meta: s.meta, 137 | invoke: { 138 | src: { 139 | type: 'task', 140 | taskId: s.taskId, 141 | task, 142 | parameters: s.parameters 143 | }, 144 | onDone: s.onDone, 145 | onError: s.onError 146 | }, 147 | on: s.on 148 | }; 149 | } 150 | 151 | export function microflowTaskMapSyncToXstateNode( 152 | s: TaskMapNodeSyncConfig, 153 | task: ITask 154 | ): XStateNodeConfig { 155 | return { 156 | type: 'atomic', 157 | meta: s.meta, 158 | invoke: { 159 | src: { 160 | type: 'task', 161 | taskId: s.taskId, 162 | task, 163 | isMap: true, 164 | itemsPath: s.itemsPath ? s.itemsPath : '$', 165 | parameters: s.parameters 166 | }, 167 | onDone: s.onDone, 168 | onError: s.onError 169 | }, 170 | on: s.on 171 | }; 172 | } 173 | 174 | export function microflowAtomicToXstateNode( 175 | s: AtomicNodeConfig 176 | ): XStateNodeConfig { 177 | return { 178 | type: 'atomic', 179 | meta: s.meta, 180 | on: s.on 181 | }; 182 | } 183 | 184 | export function microflowPassToXstateNode( 185 | s: PassNodeConfig 186 | ): XStateNodeConfig { 187 | return { 188 | type: 'atomic', 189 | meta: s.meta, 190 | invoke: { 191 | src: { 192 | type: 'transform' 193 | }, 194 | onDone: s.onDone, 195 | onError: s.onError 196 | } 197 | }; 198 | } 199 | 200 | export function microflowFinalToXstateNode( 201 | s: FinalNodeConfig 202 | ): XStateNodeConfig { 203 | return { 204 | type: 'final', 205 | meta: s.meta 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { JSONPath } from 'jsonpath-plus'; 2 | import * as _ from 'lodash'; 3 | import * as handlebars from 'handlebars'; 4 | import { ITransform } from '../types'; 5 | 6 | handlebars.registerHelper('json', function(context) { 7 | return JSON.stringify(context); 8 | }); 9 | 10 | const transform = >( 11 | obj: T, 12 | root: Record = {}, 13 | def: Record = root._, 14 | original = true 15 | ): T => { 16 | if (_.isString(obj)) { 17 | if (_.startsWith(obj, '$.') || obj === '$') 18 | return JSONPath({ path: obj, json: root, wrap: false }); 19 | return handlebars.compile(obj)(root) as T; 20 | } else if (_.isArray(obj)) { 21 | return _.map(obj, (x) => transform(x, root, def, false)) as T; 22 | } else if (_.isObject(obj)) { 23 | return _.mapValues(obj, (x) => transform(x, root, def, false)) as T; 24 | } else if (_.isUndefined(obj)) { 25 | if (original) return def as T; 26 | else return undefined; 27 | } else { 28 | return obj; 29 | } 30 | }; 31 | 32 | const setOnPath = ( 33 | root: Record, 34 | path: string, 35 | obj: Record 36 | ): Record => { 37 | if (path === '$') return obj; 38 | if (path === undefined) return root; 39 | const lodashPath = _.trimStart(path, '$.'); 40 | return _.set(_.clone(root), lodashPath, obj); 41 | }; 42 | 43 | export { transform, setOnPath }; 44 | -------------------------------------------------------------------------------- /src/utils/task.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import bluebird from 'bluebird'; 3 | import { JSONPath } from 'jsonpath-plus'; 4 | import jwt from 'jsonwebtoken'; 5 | import { InvokeCreator } from 'xstate'; 6 | import { transform } from './index'; 7 | 8 | export const taskCreator = ( 9 | secret: jwt.Secret, 10 | signOptions: jwt.SignOptions 11 | ): InvokeCreator => async ( 12 | ctx: Record, 13 | { data }, 14 | { src } 15 | ) => { 16 | const { taskEventSuffix, task, parameters, itemsPath, isMap } = src; 17 | const token = jwt.sign( 18 | { workflowInstanceId: ctx.wfid, taskEventSuffix }, 19 | secret, 20 | signOptions 21 | ); 22 | const dataInputs = isMap 23 | ? JSONPath({ 24 | path: itemsPath, 25 | json: data 26 | })[0] || [] 27 | : [data]; 28 | const response = await bluebird.map(dataInputs, async (value, index) => { 29 | const enrichedParameters = transform(parameters, { 30 | _: data, 31 | _task: { 32 | executionId: ctx.wfid, 33 | name: ctx.name, 34 | token, 35 | item: { value, index } 36 | }, 37 | _env: process.env 38 | }); 39 | const taskResolved = transform(task.config, enrichedParameters); 40 | const res = await axios.request(taskResolved); 41 | return res.data; 42 | }); 43 | return isMap ? response : response[0]; 44 | }; 45 | 46 | export const transformerInvoke: InvokeCreator = async ( 47 | _ctx: Record, 48 | { data } 49 | ) => { 50 | return data; 51 | }; 52 | -------------------------------------------------------------------------------- /src/utils/xstate.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import { 3 | AnyEventObject, 4 | Machine, 5 | MachineConfig, 6 | StateMachine, 7 | Typestate 8 | } from 'xstate'; 9 | import { WorkflowEvent } from '../types'; 10 | import { taskCreator, transformerInvoke } from './task'; 11 | 12 | export function getMachine( 13 | config: MachineConfig, 14 | secret: jwt.Secret, 15 | signOptions: jwt.SignOptions 16 | ): StateMachine> { 17 | return Machine(config, { 18 | services: { 19 | task: taskCreator(secret, signOptions), 20 | transform: transformerInvoke 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/atomicNodeInitial.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | jest.mock('axios'); 3 | const mockedAxios = axios as jest.Mocked; 4 | mockedAxios.request.mockResolvedValue({}); 5 | import { Microflow } from '../src/microflow'; 6 | 7 | const flow = new Microflow({ 8 | jwt: { 9 | secretOrPublicKey: 'shhhhh', 10 | sign: { 11 | expiresIn: '24h' 12 | } 13 | } 14 | }); 15 | 16 | test('test transitions', async () => { 17 | const task = await flow.task.create({ 18 | id: 'airflow', 19 | type: 'http', 20 | config: { 21 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 22 | headers: { 23 | 'Cache-Control': 'no-cache', 24 | 'Content-Type': 'application/json' 25 | }, 26 | data: { 27 | conf: { 28 | actualData: '$.data', 29 | token: '$.token', 30 | envKey: '$.envKey', 31 | executionId: '$.executionId' 32 | } 33 | }, 34 | method: 'post' 35 | } 36 | }); 37 | 38 | const { id: taskId } = await task.data(); 39 | 40 | const workflow = await flow.workflow.create({ 41 | id: 'sample', 42 | config: { 43 | initial: 'waiting', 44 | states: { 45 | waiting: { 46 | type: 'atomic', 47 | on: { 48 | start_test: { 49 | target: 'auto_test_1', 50 | resultPath: '$.trial' 51 | } 52 | } 53 | }, 54 | auto_test_1: { 55 | type: 'task', 56 | taskId, 57 | parameters: { 58 | dagId: 'dag1', 59 | data: '$._', 60 | token: '$._task.token', 61 | envKey: '$._env.myKey1', 62 | executionId: '$._task.executionId' 63 | }, 64 | onDone: { 65 | target: 'ready_for_approval', 66 | resultSelector: { 67 | a: 'a', 68 | b: 'b', 69 | out: '$._' 70 | }, 71 | resultPath: '$.pipeline1.success' 72 | }, 73 | onError: { 74 | target: 'failed', 75 | resultSelector: { 76 | c: 'c', 77 | d: 'd', 78 | out: '$._' 79 | }, 80 | resultPath: '$.pipeline1.error' 81 | } 82 | }, 83 | ready_for_approval: { 84 | type: 'atomic', 85 | on: { 86 | reject: { 87 | target: 'failed', 88 | resultPath: '$.reject.data' 89 | }, 90 | approve: { 91 | target: 'auto_test_2', 92 | resultPath: '$.approval.data' 93 | } 94 | } 95 | }, 96 | auto_test_2: { 97 | type: 'task', 98 | taskId, 99 | parameters: { 100 | dagId: 'dag2', 101 | data: 'karan', 102 | token: '$._task.token', 103 | envKey: '$._env.myKey2', 104 | executionId: '$._task.executionId' 105 | }, 106 | onDone: { 107 | target: 'done', 108 | resultSelector: { 109 | e: 'e', 110 | out: '$._' 111 | }, 112 | resultPath: '$.pipeline2.success' 113 | }, 114 | onError: { 115 | target: 'failed', 116 | resultSelector: { 117 | f: 'f', 118 | out: '$._' 119 | }, 120 | resultPath: '$.pipeline2.error' 121 | } 122 | }, 123 | done: { 124 | type: 'final' 125 | }, 126 | failed: { 127 | type: 'final' 128 | } 129 | } 130 | } 131 | }); 132 | 133 | const execution = await workflow.start({ 134 | input1: 'karan', 135 | input2: 'chhabra' 136 | }); 137 | 138 | await execution.send({ 139 | type: 'start_test', 140 | data: { a: 1, b: 2 } 141 | }); 142 | 143 | await execution.send({ 144 | type: 'success-auto_test_1', 145 | data: { 146 | ok: 'cupid' 147 | } 148 | }); 149 | 150 | await execution.send({ 151 | type: 'approve', 152 | data: { 153 | notok: 'cupido' 154 | } 155 | }); 156 | 157 | await execution.send({ 158 | type: 'success-auto_test_2', 159 | data: { 160 | c: 'wee' 161 | } 162 | }); 163 | 164 | const { completed, output } = await execution.data(); 165 | expect(completed).toBe(true); 166 | expect(output).toMatchObject({ 167 | input1: 'karan', 168 | input2: 'chhabra', 169 | trial: { a: 1, b: 2 }, 170 | pipeline1: { 171 | success: { 172 | a: 'a', 173 | b: 'b', 174 | out: { 175 | ok: 'cupid' 176 | } 177 | } 178 | }, 179 | approval: { 180 | data: { 181 | notok: 'cupido' 182 | } 183 | }, 184 | pipeline2: { 185 | success: { 186 | e: 'e', 187 | out: { 188 | c: 'wee' 189 | } 190 | } 191 | } 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /tests/taskMapSyncNode.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | jest.mock('axios'); 3 | const mockedAxios = axios as jest.Mocked; 4 | mockedAxios.request.mockResolvedValue({}); 5 | import { Microflow } from '../src/microflow'; 6 | 7 | const flow = new Microflow({ 8 | jwt: { 9 | secretOrPublicKey: 'shhhhh', 10 | sign: { 11 | expiresIn: '24h' 12 | } 13 | } 14 | }); 15 | 16 | test('test transitions', async () => { 17 | const task = await flow.task.create({ 18 | id: 'airflow', 19 | type: 'http', 20 | config: { 21 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 22 | headers: { 23 | 'Cache-Control': 'no-cache', 24 | 'Content-Type': 'application/json' 25 | }, 26 | data: { 27 | conf: { 28 | actualData: '$.data', 29 | token: '$.token', 30 | envKey: '$.envKey', 31 | executionId: '$.executionId' 32 | } 33 | }, 34 | method: 'post' 35 | } 36 | }); 37 | 38 | const { id: taskId } = await task.data(); 39 | 40 | // Create a workflow 41 | const workflow = await flow.workflow.create({ 42 | id: 'sample', 43 | config: { 44 | initial: 'auto_test_1', 45 | states: { 46 | auto_test_1: { 47 | type: 'taskMapSync', 48 | taskId, 49 | itemsPath: '$.iterateOn', 50 | parameters: { 51 | dagId: 'dag1', 52 | data: '$._', 53 | token: '$._task.token', 54 | envKey: '$._env.myKey1', 55 | executionId: '$._task.executionId' 56 | }, 57 | onDone: { 58 | target: 'ready_for_approval', 59 | resultSelector: { 60 | a: 'a', 61 | b: 'b', 62 | out: '$._' 63 | }, 64 | resultPath: '$.pipeline1.success' 65 | }, 66 | onError: { 67 | target: 'failed', 68 | resultSelector: { 69 | c: 'c', 70 | d: 'd', 71 | out: '$._' 72 | }, 73 | resultPath: '$.pipeline1.error' 74 | } 75 | }, 76 | ready_for_approval: { 77 | type: 'atomic', 78 | on: { 79 | reject: { 80 | target: 'failed', 81 | resultPath: '$.reject.data' 82 | }, 83 | approve: { 84 | target: 'auto_test_2', 85 | resultPath: '$.approval.data' 86 | } 87 | } 88 | }, 89 | auto_test_2: { 90 | type: 'taskSync', 91 | taskId, 92 | parameters: { 93 | dagId: 'dag2', 94 | data: '$._', 95 | token: '$._task.token', 96 | envKey: '$._env.myKey1', 97 | executionId: '$._task.executionId' 98 | }, 99 | onDone: { 100 | target: 'done', 101 | resultSelector: { 102 | e: 'e', 103 | out: '$._' 104 | }, 105 | resultPath: '$.pipeline2.success' 106 | }, 107 | onError: { 108 | target: 'failed', 109 | resultSelector: { 110 | f: 'f', 111 | out: '$._' 112 | }, 113 | resultPath: '$.pipeline2.error' 114 | } 115 | }, 116 | done: { 117 | type: 'final' 118 | }, 119 | failed: { 120 | type: 'final' 121 | } 122 | } 123 | } 124 | }); 125 | 126 | // start an execution with initial data 127 | const execution = await workflow.start({ 128 | input1: 'val1', 129 | input2: 'val2', 130 | iterateOn: [1, 2, 3] 131 | }); 132 | 133 | await execution.send({ 134 | type: 'approve', 135 | data: { 136 | message: 'The acceptance test was fine' 137 | } 138 | }); 139 | 140 | const { completed, output } = await execution.data(); 141 | expect(completed).toBe(true); 142 | expect(output).toMatchObject({ 143 | input1: 'val1', 144 | input2: 'val2', 145 | pipeline1: { 146 | success: [ 147 | { 148 | a: 'a', 149 | b: 'b', 150 | out: {} 151 | }, 152 | { 153 | a: 'a', 154 | b: 'b', 155 | out: {} 156 | }, 157 | { 158 | a: 'a', 159 | b: 'b', 160 | out: {} 161 | } 162 | ] 163 | }, 164 | approval: { data: { message: 'The acceptance test was fine' } }, 165 | pipeline2: { 166 | success: { 167 | e: 'e', 168 | out: {} 169 | } 170 | } 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /tests/taskNodeInitial.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | jest.mock('axios'); 3 | const mockedAxios = axios as jest.Mocked; 4 | mockedAxios.request.mockResolvedValue({}); 5 | import { Microflow } from '../src/microflow'; 6 | 7 | const flow = new Microflow({ 8 | jwt: { 9 | secretOrPublicKey: 'shhhhh', 10 | sign: { 11 | expiresIn: '24h' 12 | } 13 | } 14 | }); 15 | 16 | test('test transitions', async () => { 17 | const task = await flow.task.create({ 18 | id: 'airflow', 19 | type: 'http', 20 | config: { 21 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 22 | headers: { 23 | 'Cache-Control': 'no-cache', 24 | 'Content-Type': 'application/json' 25 | }, 26 | data: { 27 | conf: { 28 | actualData: '$.data', 29 | token: '$.token', 30 | envKey: '$.envKey', 31 | executionId: '$.executionId' 32 | } 33 | }, 34 | method: 'post' 35 | } 36 | }); 37 | 38 | const { id: taskId } = await task.data(); 39 | 40 | // Create a workflow 41 | const workflow = await flow.workflow.create({ 42 | id: 'sample', 43 | config: { 44 | initial: 'auto_test_1', 45 | states: { 46 | auto_test_1: { 47 | type: 'task', 48 | taskId, 49 | parameters: { 50 | dagId: 'dag1', 51 | data: '$._', 52 | token: '$._task.token', 53 | envKey: '$._env.myKey1', 54 | executionId: '$._task.executionId' 55 | }, 56 | onDone: { 57 | target: 'ready_for_approval', 58 | resultSelector: { 59 | a: 'a', 60 | b: 'b', 61 | out: '$._' 62 | }, 63 | resultPath: '$.pipeline1.success' 64 | }, 65 | onError: { 66 | target: 'failed', 67 | resultSelector: { 68 | c: 'c', 69 | d: 'd', 70 | out: '$._' 71 | }, 72 | resultPath: '$.pipeline1.error' 73 | } 74 | }, 75 | ready_for_approval: { 76 | type: 'atomic', 77 | on: { 78 | reject: { 79 | target: 'failed', 80 | resultPath: '$.reject.data' 81 | }, 82 | approve: { 83 | target: 'auto_test_2', 84 | resultPath: '$.approval.data' 85 | } 86 | } 87 | }, 88 | auto_test_2: { 89 | type: 'task', 90 | taskId, 91 | parameters: { 92 | dagId: 'dag2', 93 | data: '$._', 94 | token: '$._task.token', 95 | envKey: '$._env.myKey1', 96 | executionId: '$._task.executionId' 97 | }, 98 | onDone: { 99 | target: 'done', 100 | resultSelector: { 101 | e: 'e', 102 | out: '$._' 103 | }, 104 | resultPath: '$.pipeline2.success' 105 | }, 106 | onError: { 107 | target: 'failed', 108 | resultSelector: { 109 | f: 'f', 110 | out: '$._' 111 | }, 112 | resultPath: '$.pipeline2.error' 113 | } 114 | }, 115 | done: { 116 | type: 'final' 117 | }, 118 | failed: { 119 | type: 'final' 120 | } 121 | } 122 | } 123 | }); 124 | 125 | // start an execution with initial data 126 | const execution = await workflow.start({ 127 | input1: 'val1', 128 | input2: 'val2' 129 | }); 130 | 131 | // Sending events to an execution 132 | await execution.send({ 133 | type: 'success-auto_test_1', 134 | data: { 135 | test_a_result: true, 136 | test_b_result: false 137 | } 138 | }); 139 | 140 | await execution.send({ 141 | type: 'approve', 142 | data: { 143 | message: 'The acceptance test was fine' 144 | } 145 | }); 146 | 147 | await execution.send({ 148 | type: 'success-auto_test_2', 149 | data: { 150 | test_c_result: true 151 | } 152 | }); 153 | 154 | const { completed, output } = await execution.data(); 155 | expect(completed).toBe(true); 156 | expect(output).toMatchObject({ 157 | input1: 'val1', 158 | input2: 'val2', 159 | pipeline1: { 160 | success: { 161 | a: 'a', 162 | b: 'b', 163 | out: { 164 | test_a_result: true, 165 | test_b_result: false 166 | } 167 | } 168 | }, 169 | approval: { 170 | data: { 171 | message: 'The acceptance test was fine' 172 | } 173 | }, 174 | pipeline2: { 175 | success: { 176 | e: 'e', 177 | out: { 178 | test_c_result: true 179 | } 180 | } 181 | } 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /tests/taskSyncNode.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | jest.mock('axios'); 3 | const mockedAxios = axios as jest.Mocked; 4 | mockedAxios.request.mockResolvedValue({}); 5 | import { Microflow } from '../src/microflow'; 6 | 7 | const flow = new Microflow({ 8 | jwt: { 9 | secretOrPublicKey: 'shhhhh', 10 | sign: { 11 | expiresIn: '24h' 12 | } 13 | } 14 | }); 15 | 16 | test('test transitions', async () => { 17 | const task = await flow.task.create({ 18 | id: 'airflow', 19 | type: 'http', 20 | config: { 21 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 22 | headers: { 23 | 'Cache-Control': 'no-cache', 24 | 'Content-Type': 'application/json' 25 | }, 26 | data: { 27 | conf: { 28 | actualData: '$.data', 29 | token: '$.token', 30 | envKey: '$.envKey', 31 | executionId: '$.executionId' 32 | } 33 | }, 34 | method: 'post' 35 | } 36 | }); 37 | 38 | const { id: taskId } = await task.data(); 39 | 40 | // Create a workflow 41 | const workflow = await flow.workflow.create({ 42 | id: 'sample', 43 | config: { 44 | initial: 'auto_test_1', 45 | states: { 46 | auto_test_1: { 47 | type: 'taskSync', 48 | taskId, 49 | parameters: { 50 | dagId: 'dag1', 51 | data: '$._', 52 | token: '$._task.token', 53 | envKey: '$._env.myKey1', 54 | executionId: '$._task.executionId' 55 | }, 56 | onDone: { 57 | target: 'ready_for_approval', 58 | resultSelector: { 59 | a: 'a', 60 | b: 'b', 61 | out: '$._' 62 | }, 63 | resultPath: '$.pipeline1.success' 64 | }, 65 | onError: { 66 | target: 'failed', 67 | resultSelector: { 68 | c: 'c', 69 | d: 'd', 70 | out: '$._' 71 | }, 72 | resultPath: '$.pipeline1.error' 73 | } 74 | }, 75 | ready_for_approval: { 76 | type: 'atomic', 77 | on: { 78 | reject: { 79 | target: 'failed', 80 | resultPath: '$.reject.data' 81 | }, 82 | approve: { 83 | target: 'auto_test_2', 84 | resultPath: '$.approval.data' 85 | } 86 | } 87 | }, 88 | auto_test_2: { 89 | type: 'taskSync', 90 | taskId, 91 | parameters: { 92 | dagId: 'dag2', 93 | data: '$._', 94 | token: '$._task.token', 95 | envKey: '$._env.myKey1', 96 | executionId: '$._task.executionId' 97 | }, 98 | onDone: { 99 | target: 'done', 100 | resultSelector: { 101 | e: 'e', 102 | out: '$._' 103 | }, 104 | resultPath: '$.pipeline2.success' 105 | }, 106 | onError: { 107 | target: 'failed', 108 | resultSelector: { 109 | f: 'f', 110 | out: '$._' 111 | }, 112 | resultPath: '$.pipeline2.error' 113 | } 114 | }, 115 | done: { 116 | type: 'final' 117 | }, 118 | failed: { 119 | type: 'final' 120 | } 121 | } 122 | } 123 | }); 124 | 125 | // start an execution with initial data 126 | const execution = await workflow.start({ 127 | input1: 'val1', 128 | input2: 'val2' 129 | }); 130 | 131 | await execution.send({ 132 | type: 'approve', 133 | data: { 134 | message: 'The acceptance test was fine' 135 | } 136 | }); 137 | 138 | const { completed, output } = await execution.data(); 139 | expect(completed).toBe(true); 140 | expect(output).toMatchObject({ 141 | input1: 'val1', 142 | input2: 'val2', 143 | pipeline1: { 144 | success: { 145 | a: 'a', 146 | b: 'b', 147 | out: {} 148 | } 149 | }, 150 | approval: { data: { message: 'The acceptance test was fine' } }, 151 | pipeline2: { 152 | success: { 153 | e: 'e', 154 | out: {} 155 | } 156 | } 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /tests/taskToken.test.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import axios from 'axios'; 3 | jest.mock('axios'); 4 | const mockedAxios = axios as jest.Mocked; 5 | mockedAxios.request.mockResolvedValue({}); 6 | import { Microflow } from '../src/microflow'; 7 | 8 | const getTaskToken = (id, task) => 9 | jwt.sign({ workflowInstanceId: id, taskEventSuffix: task }, 'shhhhh', { 10 | expiresIn: '24h' 11 | }); 12 | 13 | const flow = new Microflow({ 14 | jwt: { 15 | secretOrPublicKey: 'shhhhh', 16 | sign: { 17 | expiresIn: '24h' 18 | } 19 | } 20 | }); 21 | 22 | test('test transitions with tokens', async () => { 23 | const task = await flow.task.create({ 24 | id: 'airflow', 25 | type: 'http', 26 | config: { 27 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 28 | headers: { 29 | 'Cache-Control': 'no-cache', 30 | 'Content-Type': 'application/json' 31 | }, 32 | data: { 33 | conf: { 34 | actualData: '$.data', 35 | token: '$.token', 36 | envKey: '$.envKey', 37 | executionId: '$.executionId' 38 | } 39 | }, 40 | method: 'post' 41 | } 42 | }); 43 | 44 | const { id: taskId } = await task.data(); 45 | 46 | const workflow = await flow.workflow.create({ 47 | id: 'sample', 48 | config: { 49 | initial: 'waiting', 50 | states: { 51 | waiting: { 52 | type: 'atomic', 53 | on: { 54 | start_test: { 55 | target: 'auto_test_1', 56 | resultPath: '$' 57 | } 58 | } 59 | }, 60 | auto_test_1: { 61 | type: 'task', 62 | taskId: 'airflow', 63 | parameters: { 64 | dagId: 'dag1', 65 | data: '$._', 66 | token: '$._task.token', 67 | envKey: '$._env.myKey1', 68 | executionId: '$._task.executionId' 69 | }, 70 | onDone: { 71 | target: 'ready_for_approval', 72 | resultSelector: { 73 | a: 'a', 74 | b: 'b', 75 | out: '$._' 76 | }, 77 | resultPath: '$.pipeline1.success' 78 | }, 79 | onError: { 80 | target: 'failed', 81 | resultSelector: { 82 | c: 'c', 83 | d: 'd', 84 | out: '$._' 85 | }, 86 | resultPath: '$.pipeline1.error' 87 | } 88 | }, 89 | ready_for_approval: { 90 | type: 'atomic', 91 | on: { 92 | reject: { 93 | target: 'failed', 94 | resultPath: '$.reject.data' 95 | }, 96 | approve: { 97 | target: 'auto_test_2', 98 | resultPath: '$.approval.data' 99 | } 100 | } 101 | }, 102 | auto_test_2: { 103 | type: 'task', 104 | taskId: taskId, 105 | parameters: { 106 | dagId: 'dag2', 107 | data: '$._', 108 | token: '$._task.token', 109 | envKey: '$._env.myKey1', 110 | executionId: '$._task.executionId' 111 | }, 112 | onDone: { 113 | target: 'done', 114 | resultSelector: { 115 | e: 'e', 116 | out: '$._' 117 | }, 118 | resultPath: '$.pipeline2.success' 119 | }, 120 | onError: { 121 | target: 'failed', 122 | resultSelector: { 123 | f: 'f', 124 | out: '$._' 125 | }, 126 | resultPath: '$.pipeline2.error' 127 | } 128 | }, 129 | done: { 130 | type: 'final' 131 | }, 132 | failed: { 133 | type: 'final' 134 | } 135 | } 136 | } 137 | }); 138 | 139 | const execution = await workflow.start(); 140 | 141 | const { id: executionId } = await execution.data(); 142 | 143 | await execution.send({ 144 | type: 'start_test', 145 | data: { input1: 'val1', input2: 'val2' } 146 | }); 147 | 148 | await flow.sendTaskSuccess(getTaskToken(executionId, 'auto_test_1'), { 149 | test_a_result: true, 150 | test_b_result: false 151 | }); 152 | 153 | await execution.send({ 154 | type: 'approve', 155 | data: { 156 | message: 'The acceptance test was fine' 157 | } 158 | }); 159 | 160 | await flow.sendTaskSuccess(getTaskToken(executionId, 'auto_test_2'), { 161 | test_c_result: true 162 | }); 163 | 164 | const { completed, output } = await execution.data(); 165 | expect(completed).toBe(true); 166 | expect(output).toMatchObject({ 167 | input1: 'val1', 168 | input2: 'val2', 169 | pipeline1: { 170 | success: { 171 | a: 'a', 172 | b: 'b', 173 | out: { 174 | test_a_result: true, 175 | test_b_result: false 176 | } 177 | } 178 | }, 179 | approval: { 180 | data: { 181 | message: 'The acceptance test was fine' 182 | } 183 | }, 184 | pipeline2: { 185 | success: { 186 | e: 'e', 187 | out: { 188 | test_c_result: true 189 | } 190 | } 191 | } 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /tests/utils/index.test.ts: -------------------------------------------------------------------------------- 1 | import { transform, setOnPath } from '../../src/utils/index'; 2 | 3 | describe('transform function', () => { 4 | test('object input and root', () => { 5 | const output = transform( 6 | { 7 | a: '$', 8 | b: '$.key1', 9 | c: '$.jabber', 10 | d: 'constant', 11 | e: { 12 | f: 'constant', 13 | g: '$.key1', 14 | h: '$' 15 | } 16 | }, 17 | { 18 | key1: 'val1', 19 | key2: 'val2' 20 | } 21 | ); 22 | expect(output).toEqual({ 23 | a: { key1: 'val1', key2: 'val2' }, 24 | b: 'val1', 25 | c: undefined, 26 | d: 'constant', 27 | e: { f: 'constant', g: 'val1', h: { key1: 'val1', key2: 'val2' } } 28 | }); 29 | }); 30 | 31 | test('object input and root, access array indices', () => { 32 | const output = transform( 33 | { 34 | a: '$', 35 | b: '$.key1[1]', 36 | c: '$.jabber', 37 | d: 'constant', 38 | e: { 39 | f: 'constant', 40 | g: '$.key1', 41 | h: '$' 42 | } 43 | }, 44 | { 45 | key1: ['val1', 'val2'], 46 | key2: 'val3' 47 | } 48 | ); 49 | expect(output).toEqual({ 50 | a: { key1: ['val1', 'val2'], key2: 'val3' }, 51 | b: 'val2', 52 | c: undefined, 53 | d: 'constant', 54 | e: { 55 | f: 'constant', 56 | g: ['val1', 'val2'], 57 | h: { 58 | key1: ['val1', 'val2'], 59 | key2: 'val3' 60 | } 61 | } 62 | }); 63 | }); 64 | 65 | test('null input, object root', () => { 66 | const output = transform(null, { 67 | key1: 'val1', 68 | key2: 'val2' 69 | }); 70 | expect(output).toBeNull(); 71 | }); 72 | 73 | test('string input, object root', () => { 74 | const output = transform('$.key1', { 75 | key1: 'val1', 76 | key2: 'val2' 77 | }); 78 | expect(output).toEqual('val1'); 79 | }); 80 | 81 | test('object input, empty root', () => { 82 | const output = transform( 83 | { 84 | a: '$', 85 | b: '$.key1', 86 | c: '$.jabber', 87 | d: 'constant', 88 | e: { 89 | f: 'constant', 90 | g: '$.key1', 91 | h: '$' 92 | } 93 | }, 94 | {} 95 | ); 96 | expect(output).toEqual({ 97 | a: {}, 98 | b: undefined, 99 | c: undefined, 100 | d: 'constant', 101 | e: { 102 | f: 'constant', 103 | g: undefined, 104 | h: {} 105 | } 106 | }); 107 | }); 108 | 109 | test('object(with array key) input and root', () => { 110 | const output = transform( 111 | { 112 | url: 'http://localhost:1000/api/experimental/dags/{{dagId}}/dag_runs', 113 | headers: { 114 | 'Cache-Control': 'no-cache', 115 | 'Content-Type': 'application/json' 116 | }, 117 | data: { 118 | conf: { 119 | key: ['arrayVal'] 120 | } 121 | }, 122 | method: 'post' 123 | }, 124 | { 125 | dagId: 'dag1', 126 | data: '$' 127 | } 128 | ); 129 | expect(output).toEqual({ 130 | url: 'http://localhost:1000/api/experimental/dags/dag1/dag_runs', 131 | headers: { 132 | 'Cache-Control': 'no-cache', 133 | 'Content-Type': 'application/json' 134 | }, 135 | data: { 136 | conf: { 137 | key: ['arrayVal'] 138 | } 139 | }, 140 | method: 'post' 141 | }); 142 | }); 143 | }); 144 | 145 | describe('setOnPath function', () => { 146 | test('default path', () => { 147 | const output = setOnPath({ a: 1, b: 2 }, '$', { c: 3, d: 4 }); 148 | expect(output).toEqual({ c: 3, d: 4 }); 149 | }); 150 | 151 | test('empty object, default path', () => { 152 | const output = setOnPath({ a: 1, b: 2 }, '$', {}); 153 | expect(output).toEqual({}); 154 | }); 155 | 156 | test('empty object', () => { 157 | const output = setOnPath({ a: 1, b: 2 }, '$.c', {}); 158 | expect(output).toEqual({ a: 1, b: 2, c: {} }); 159 | }); 160 | 161 | test('empty root, default path', () => { 162 | const output = setOnPath({}, '$', { c: 3, d: 4 }); 163 | expect(output).toEqual({ c: 3, d: 4 }); 164 | }); 165 | 166 | test('empty root', () => { 167 | const output = setOnPath({}, '$.a', { c: 3, d: 4 }); 168 | expect(output).toEqual({ a: { c: 3, d: 4 } }); 169 | }); 170 | 171 | test('should overwrite key', () => { 172 | const output = setOnPath({ a: 1, b: 2 }, '$.b', { c: 3, d: 4 }); 173 | expect(output).toEqual({ a: 1, b: { c: 3, d: 4 } }); 174 | }); 175 | 176 | test('should write nested key', () => { 177 | const output = setOnPath({ a: 1, b: 2 }, '$.c.d', { c: 3, d: 4 }); 178 | expect(output).toEqual({ a: 1, b: 2, c: { d: { c: 3, d: 4 } } }); 179 | }); 180 | 181 | test('should overwrite nested key', () => { 182 | const output = setOnPath({ a: 1, b: 2, c: { d: 3 } }, '$.c.d', { 183 | c: 3, 184 | d: 4 185 | }); 186 | expect(output).toEqual({ a: 1, b: 2, c: { d: { c: 3, d: 4 } } }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "module": "commonjs", 10 | "allowJs": true, 11 | "target": "ES2018" 12 | }, 13 | "include": ["./src/**/*"] 14 | } 15 | --------------------------------------------------------------------------------