diff --git a/examples/node/recognize.js b/examples/node/recognize.js
index d283944..804820a 100755
--- a/examples/node/recognize.js
+++ b/examples/node/recognize.js
@@ -1,23 +1,22 @@
 #!/usr/bin/env node
 const path = require('path');
-const { TesseractWorker } = require('../../');
+const { createScheduler, createWorker, createJob, OEM } = require('../../');
 
 const [,, imagePath] = process.argv;
 const image = path.resolve(__dirname, (imagePath || '../../tests/assets/images/cosmic.png'));
-const tessWorker = new TesseractWorker();
 
 console.log(`Recognizing ${image}`);
 
-tessWorker.recognize(image)
-  .progress((info) => {
-    console.log(info);
-  })
-  .then((data) => {
-    console.log(data.text);
-  })
-  .catch((err) => {
-    console.log('Error\n', err);
-  })
-  .finally(() => {
-    process.exit();
+(async () => {
+  const scheduler = createScheduler();
+  const worker = createWorker();
+  await worker.load();
+  await worker.loadLanguage('osd');
+  await worker.initialize('osd', {
+    tessedit_ocr_engine_mode: OEM.OSD_ONLY,
   });
+  scheduler.addWorker(worker);
+  const data = await scheduler.addJob(createJob('detect', { image }));
+  console.log(data);
+  scheduler.terminate();
+})();
diff --git a/package-lock.json b/package-lock.json
index 4c2b0b1..a6bc2ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -214,22 +214,22 @@
           }
         },
         "@babel/parser": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz",
-          "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==",
+          "version": "7.4.5",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz",
+          "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==",
           "dev": true
         },
         "@babel/traverse": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz",
-          "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==",
+          "version": "7.4.5",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz",
+          "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==",
           "dev": true,
           "requires": {
             "@babel/code-frame": "^7.0.0",
             "@babel/generator": "^7.4.4",
             "@babel/helper-function-name": "^7.1.0",
             "@babel/helper-split-export-declaration": "^7.4.4",
-            "@babel/parser": "^7.4.4",
+            "@babel/parser": "^7.4.5",
             "@babel/types": "^7.4.4",
             "debug": "^4.1.0",
             "globals": "^11.1.0",
@@ -376,848 +376,6 @@
         "@babel/types": "^7.0.0"
       }
     },
-    "@babel/helper-module-transforms": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz",
-      "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-module-imports": "^7.0.0",
-        "@babel/helper-simple-access": "^7.1.0",
-        "@babel/helper-split-export-declaration": "^7.4.4",
-        "@babel/template": "^7.4.4",
-        "@babel/types": "^7.4.4",
-        "lodash": "^4.17.11"
-      },
-      "dependencies": {
-        "@babel/helper-split-export-declaration": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
-          "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/parser": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz",
-          "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==",
-          "dev": true
-        },
-        "@babel/template": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
-          "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "@babel/parser": "^7.4.4",
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-optimise-call-expression": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
-      "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-plugin-utils": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
-      "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
-      "dev": true
-    },
-    "@babel/helper-regex": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz",
-      "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==",
-      "dev": true,
-      "requires": {
-        "lodash": "^4.17.11"
-      },
-      "dependencies": {
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-remap-async-to-generator": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz",
-      "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-annotate-as-pure": "^7.0.0",
-        "@babel/helper-wrap-function": "^7.1.0",
-        "@babel/template": "^7.1.0",
-        "@babel/traverse": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-replace-supers": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz",
-      "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-member-expression-to-functions": "^7.0.0",
-        "@babel/helper-optimise-call-expression": "^7.0.0",
-        "@babel/traverse": "^7.4.4",
-        "@babel/types": "^7.4.4"
-      },
-      "dependencies": {
-        "@babel/generator": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",
-          "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4",
-            "jsesc": "^2.5.1",
-            "lodash": "^4.17.11",
-            "source-map": "^0.5.0",
-            "trim-right": "^1.0.1"
-          }
-        },
-        "@babel/helper-split-export-declaration": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
-          "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/parser": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz",
-          "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==",
-          "dev": true
-        },
-        "@babel/traverse": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz",
-          "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "@babel/generator": "^7.4.4",
-            "@babel/helper-function-name": "^7.1.0",
-            "@babel/helper-split-export-declaration": "^7.4.4",
-            "@babel/parser": "^7.4.4",
-            "@babel/types": "^7.4.4",
-            "debug": "^4.1.0",
-            "globals": "^11.1.0",
-            "lodash": "^4.17.11"
-          }
-        },
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        },
-        "ms": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
-          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-simple-access": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz",
-      "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==",
-      "dev": true,
-      "requires": {
-        "@babel/template": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-split-export-declaration": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
-      "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-wrap-function": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
-      "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-function-name": "^7.1.0",
-        "@babel/template": "^7.1.0",
-        "@babel/traverse": "^7.1.0",
-        "@babel/types": "^7.2.0"
-      },
-      "dependencies": {
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helpers": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz",
-      "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==",
-      "dev": true,
-      "requires": {
-        "@babel/template": "^7.4.4",
-        "@babel/traverse": "^7.4.4",
-        "@babel/types": "^7.4.4"
-      },
-      "dependencies": {
-        "@babel/generator": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",
-          "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4",
-            "jsesc": "^2.5.1",
-            "lodash": "^4.17.11",
-            "source-map": "^0.5.0",
-            "trim-right": "^1.0.1"
-          }
-        },
-        "@babel/helper-split-export-declaration": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
-          "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/parser": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz",
-          "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==",
-          "dev": true
-        },
-        "@babel/template": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
-          "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "@babel/parser": "^7.4.4",
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/traverse": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz",
-          "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "@babel/generator": "^7.4.4",
-            "@babel/helper-function-name": "^7.1.0",
-            "@babel/helper-split-export-declaration": "^7.4.4",
-            "@babel/parser": "^7.4.4",
-            "@babel/types": "^7.4.4",
-            "debug": "^4.1.0",
-            "globals": "^11.1.0",
-            "lodash": "^4.17.11"
-          }
-        },
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        },
-        "ms": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
-          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/highlight": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
-      "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
-      "dev": true,
-      "requires": {
-        "chalk": "^2.0.0",
-        "esutils": "^2.0.2",
-        "js-tokens": "^4.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "3.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-          "dev": true,
-          "requires": {
-            "color-convert": "^1.9.0"
-          }
-        },
-        "chalk": {
-          "version": "2.4.1",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
-          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^3.2.1",
-            "escape-string-regexp": "^1.0.5",
-            "supports-color": "^5.3.0"
-          }
-        },
-        "js-tokens": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-          "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        }
-      }
-    },
-    "@babel/parser": {
-      "version": "7.1.6",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.6.tgz",
-      "integrity": "sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==",
-      "dev": true
-    },
-    "@babel/plugin-proposal-async-generator-functions": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz",
-      "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/helper-remap-async-to-generator": "^7.1.0",
-        "@babel/plugin-syntax-async-generators": "^7.2.0"
-      }
-    },
-    "@babel/plugin-proposal-json-strings": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
-      "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/plugin-syntax-json-strings": "^7.2.0"
-      }
-    },
-    "@babel/plugin-proposal-object-rest-spread": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz",
-      "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/plugin-syntax-object-rest-spread": "^7.2.0"
-      }
-    },
-    "@babel/plugin-proposal-optional-catch-binding": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz",
-      "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/plugin-syntax-optional-catch-binding": "^7.2.0"
-      }
-    },
-    "@babel/plugin-proposal-unicode-property-regex": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz",
-      "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/helper-regex": "^7.4.4",
-        "regexpu-core": "^4.5.4"
-      }
-    },
-    "@babel/plugin-syntax-async-generators": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz",
-      "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-syntax-json-strings": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
-      "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-syntax-object-rest-spread": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
-      "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-syntax-optional-catch-binding": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz",
-      "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-arrow-functions": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
-      "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-async-to-generator": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz",
-      "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-module-imports": "^7.0.0",
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/helper-remap-async-to-generator": "^7.1.0"
-      }
-    },
-    "@babel/plugin-transform-block-scoped-functions": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz",
-      "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-block-scoping": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz",
-      "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "lodash": "^4.17.11"
-      },
-      "dependencies": {
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/plugin-transform-classes": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz",
-      "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-annotate-as-pure": "^7.0.0",
-        "@babel/helper-define-map": "^7.4.4",
-        "@babel/helper-function-name": "^7.1.0",
-        "@babel/helper-optimise-call-expression": "^7.0.0",
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/helper-replace-supers": "^7.4.4",
-        "@babel/helper-split-export-declaration": "^7.4.4",
-        "globals": "^11.1.0"
-      },
-      "dependencies": {
-        "@babel/helper-split-export-declaration": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
-          "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/plugin-transform-computed-properties": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz",
-      "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-destructuring": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz",
-      "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-dotall-regex": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz",
-      "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0",
-        "@babel/helper-regex": "^7.4.4",
-        "regexpu-core": "^4.5.4"
-      }
-    },
-    "@babel/plugin-transform-duplicate-keys": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz",
-      "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-exponentiation-operator": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz",
-      "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0",
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/helper-annotate-as-pure": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
-      "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-builder-binary-assignment-operator-visitor": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz",
-      "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-explode-assignable-expression": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-call-delegate": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz",
-      "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-hoist-variables": "^7.4.4",
-        "@babel/traverse": "^7.4.4",
-        "@babel/types": "^7.4.4"
-      },
-      "dependencies": {
-        "@babel/generator": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",
-          "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4",
-            "jsesc": "^2.5.1",
-            "lodash": "^4.17.11",
-            "source-map": "^0.5.0",
-            "trim-right": "^1.0.1"
-          }
-        },
-        "@babel/helper-split-export-declaration": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
-          "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
-          "dev": true,
-          "requires": {
-            "@babel/types": "^7.4.4"
-          }
-        },
-        "@babel/parser": {
-          "version": "7.4.5",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz",
-          "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==",
-          "dev": true
-        },
-        "@babel/traverse": {
-          "version": "7.4.5",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz",
-          "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "@babel/generator": "^7.4.4",
-            "@babel/helper-function-name": "^7.1.0",
-            "@babel/helper-split-export-declaration": "^7.4.4",
-            "@babel/parser": "^7.4.5",
-            "@babel/types": "^7.4.4",
-            "debug": "^4.1.0",
-            "globals": "^11.1.0",
-            "lodash": "^4.17.11"
-          }
-        },
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        },
-        "ms": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
-          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-define-map": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz",
-      "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-function-name": "^7.1.0",
-        "@babel/types": "^7.4.4",
-        "lodash": "^4.17.11"
-      },
-      "dependencies": {
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-explode-assignable-expression": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz",
-      "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==",
-      "dev": true,
-      "requires": {
-        "@babel/traverse": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "@babel/helper-function-name": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
-      "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-call-delegate": "^7.4.4",
-        "@babel/helper-get-function-arity": "^7.0.0",
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/plugin-transform-property-literals": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz",
-      "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.0.0"
-      }
-    },
-    "@babel/helper-hoist-variables": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz",
-      "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.4.4"
-      },
-      "dependencies": {
-        "@babel/types": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
-          "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
-          "dev": true,
-          "requires": {
-            "esutils": "^2.0.2",
-            "lodash": "^4.17.11",
-            "to-fast-properties": "^2.0.0"
-          }
-        },
-        "lodash": {
-          "version": "4.17.11",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
-          "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
-          "dev": true
-        }
-      }
-    },
-    "@babel/helper-member-expression-to-functions": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz",
-      "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==",
-      "dev": true,
-      "requires": {
-        "regenerator-transform": "^0.13.4"
-      }
-    },
-    "@babel/helper-module-imports": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
-      "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.0.0"
-      }
-    },
     "@babel/helper-module-transforms": {
       "version": "7.4.4",
       "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz",
diff --git a/src/common/createJob.js b/src/common/createJob.js
new file mode 100644
index 0000000..a7e3b48
--- /dev/null
+++ b/src/common/createJob.js
@@ -0,0 +1,27 @@
+const { sendPacket } = require('../node');
+
+let jobCounter = 0;
+
+module.exports = (
+  action,
+  payload,
+) => {
+  jobCounter += 1;
+  const id = `Job-${jobCounter}-${Math.random().toString(16).slice(3, 8)}`;
+
+  const start = (worker) => {
+    console.log(`[${worker.id}]: Start ${id}, action=${action}`);
+    sendPacket(worker, {
+      workerId: worker.id,
+      jobId: id,
+      action,
+      payload,
+    });
+  };
+
+  return {
+    id,
+    action,
+    start,
+  };
+};
diff --git a/src/common/createScheduler.js b/src/common/createScheduler.js
new file mode 100644
index 0000000..23b3435
--- /dev/null
+++ b/src/common/createScheduler.js
@@ -0,0 +1,57 @@
+module.exports = () => {
+  const workers = {};
+  const runningJobs = {};
+  let jobQueue = [];
+
+  const dequeue = () => {
+    if (jobQueue.length !== 0) {
+      const wIds = Object.keys(workers);
+      for (let i = 0; i < wIds.length; i += 1) {
+        if (typeof runningJobs[wIds[i]] === 'undefined') {
+          jobQueue[0](workers[wIds[i]]);
+          break;
+        }
+      }
+    }
+  };
+
+  const queue = job => (
+    new Promise((resolve, reject) => {
+      jobQueue.push((w) => {
+        const { action } = job;
+        jobQueue.shift();
+        w.setResolve(action, (data) => {
+          delete runningJobs[w.id];
+          dequeue();
+          resolve(data);
+        });
+        w.setReject(action, reject);
+        runningJobs[w.id] = job;
+        job.start(w);
+      });
+      dequeue();
+    })
+  );
+
+  const addWorker = (w) => {
+    workers[w.id] = w;
+    return w.id;
+  };
+
+  const addJob = job => (
+    queue(job)
+  );
+
+  const terminate = () => {
+    Object.keys(workers).forEach((id) => {
+      workers[id].terminate();
+    });
+    jobQueue = [];
+  };
+
+  return {
+    addWorker,
+    addJob,
+    terminate,
+  };
+};
diff --git a/src/common/createTesseract.js b/src/common/createTesseract.js
new file mode 100644
index 0000000..fe0c1d1
--- /dev/null
+++ b/src/common/createTesseract.js
@@ -0,0 +1,7 @@
+module.exports = (options = {}, nWorkers = 1) => {
+  return {
+    init: () => {},
+    loadLanguauge: () => {},
+    recognize: () => {},
+  };
+};
diff --git a/src/common/createWorker.js b/src/common/createWorker.js
new file mode 100644
index 0000000..0c56e02
--- /dev/null
+++ b/src/common/createWorker.js
@@ -0,0 +1,129 @@
+const { isBrowser } = require('./env');
+const resolveURL = isBrowser ? require('resolve-url') : s => s; // eslint-disable-line
+const circularize = require('./circularize');
+const createJob = require('./createJob');
+const { defaultParams } = require('./options');
+const {
+  defaultOptions,
+  spawnWorker,
+  terminateWorker,
+  setOnMessage,
+} = require('../node');
+
+let workerCounter = 0;
+
+const resolvePaths = (options) => {
+  const opts = { ...options };
+  ['corePath', 'workerPath', 'langPath'].forEach((key) => {
+    if (typeof options[key] !== 'undefined') {
+      opts[key] = resolveURL(opts[key]);
+    }
+  });
+  return opts;
+};
+
+module.exports = (options = {}) => {
+  workerCounter += 1;
+  const id = `Worker-${workerCounter}-${Math.random().toString(16).slice(3, 8)}`;
+  const opts = resolvePaths({
+    ...defaultOptions,
+    ...options,
+  });
+  const { logger } = opts;
+  const resolves = {};
+  const rejects = {};
+  let worker = spawnWorker(opts);
+
+  const setResolve = (action, res) => {
+    resolves[action] = res;
+  };
+
+  const setReject = (action, rej) => {
+    rejects[action] = rej;
+  };
+
+  const load = () => (
+    new Promise((resolve, reject) => {
+      const job = createJob(
+        'load',
+        opts,
+      );
+      setResolve('load', resolve);
+      setReject('load', reject);
+      job.start({ worker, id });
+    })
+  );
+
+  const loadLanguage = (langs = 'eng') => (
+    new Promise((resolve, reject) => {
+      const job = createJob(
+        'load-language',
+        {
+          langs,
+          options: opts,
+        },
+      );
+      setResolve('load-language', resolve);
+      setReject('load-language', reject);
+      job.start({ worker, id });
+    })
+  );
+
+  const initialize = (langs = 'eng', params = {}) => (
+    new Promise((resolve, reject) => {
+      const job = createJob(
+        'initialize',
+        {
+          langs,
+          params: {
+            ...defaultParams,
+            ...params,
+          },
+        },
+      );
+      setResolve('initialize', resolve);
+      setReject('initialize', reject);
+      job.start({ worker, id });
+    })
+  );
+
+  const terminate = () => {
+    if (worker !== null) {
+      terminateWorker({ worker });
+      worker = null;
+    }
+  };
+
+  setOnMessage(worker, (packet) => {
+    const { status, action, data } = packet;
+    if (status === 'resolve') {
+      if (action === 'load') {
+        resolves.load(data);
+      } else if (action === 'initialize') {
+        resolves.initialize({ id });
+      } else if (action === 'load-language') {
+        resolves['load-language'](data);
+      } else if (action === 'recognize') {
+        resolves.recognize(circularize(data));
+      } else if (action === 'detect') {
+        resolves.detect(data);
+      }
+    } else if (status === 'reject') {
+      rejects[action](data);
+      throw Error(data);
+    } else if (status === 'progress') {
+      logger(data);
+    }
+  });
+
+  return {
+    id,
+    worker,
+    setResolve,
+    setReject,
+    load,
+    loadLanguage,
+    initialize,
+    terminate,
+  };
+};
diff --git a/src/common/env.js b/src/common/env.js
new file mode 100644
index 0000000..f8e49ad
--- /dev/null
+++ b/src/common/env.js
@@ -0,0 +1 @@
+exports.isBrowser = (typeof window !== 'undefined') && (typeof window.document !== 'undefined');
diff --git a/src/common/options.js b/src/common/options.js
index a86a263..65e7936 100644
--- a/src/common/options.js
+++ b/src/common/options.js
@@ -12,6 +12,7 @@ module.exports = {
      * Use BlobURL for worker script by default
      */
     workerBlobURL: true,
+    logger: () => {},
   },
   /*
    * default params for recognize()
diff --git a/src/common/utils.js b/src/common/utils.js
new file mode 100644
index 0000000..51edf83
--- /dev/null
+++ b/src/common/utils.js
@@ -0,0 +1,87 @@
+const { readImage } = require('tesseract.js-utils');
+
+/**
+ * setImage
+ *
+ * @name setImage
+ * @function set image in tesseract for recognition
+ * @access public
+ * @param {array} image - binary array in array format
+ * @returns {number} - an emscripten pointer of the image
+ */
+exports.setImage = (TessModule, api, image, params) => {
+  const {
+    tessjs_image_rectangle_left: left,
+    tessjs_image_rectangle_top: top,
+    tessjs_image_rectangle_width: width,
+    tessjs_image_rectangle_height: height,
+  } = params;
+  const {
+    w, h, bytesPerPixel, data, pix,
+  } = readImage(TessModule, Array.from(image));
+
+  /*
+   * As some image format (ex. bmp) is not supported natiely by tesseract,
+   * sometimes it will not return pix directly, but data and bytesPerPixel
+   * for another SetImage usage.
+   *
+   */
+  if (data === null) {
+    api.SetImage(pix);
+  } else {
+    api.SetImage(data, w, h, bytesPerPixel, w * bytesPerPixel);
+  }
+  api.SetRectangle(
+    (left < 0) ? 0 : left,
+    (top < 0) ? 0 : top,
+    (width < 0) ? w : width,
+    (height < 0) ? h : height,
+  );
+  return data === null ? pix : data;
+};
+
+exports.getLangsStr = langs => (
+  typeof langs === 'string'
+    ? langs
+    : langs.map(lang => (typeof lang === 'string' ? lang : lang.data)).join('+')
+);
+
+/**
+ * handleOutput
+ *
+ * @name handleOutput
+ * @function handle file output
+ * @access private
+ * @param {object} customParams - an object of params
+ */
+exports.getFiles = (TessModule, api, adapter, params) => {
+  let files = {};
+  const {
+    tessjs_create_pdf,
+    tessjs_textonly_pdf,
+    tessjs_pdf_name,
+    tessjs_pdf_title,
+    tessjs_pdf_auto_download,
+    tessjs_pdf_bin,
+  } = params;
+
+  if (tessjs_create_pdf === '1') {
+    const pdfRenderer = new TessModule.TessPDFRenderer(tessjs_pdf_name, '/', tessjs_textonly_pdf === '1');
+    pdfRenderer.BeginDocument(tessjs_pdf_title);
+    pdfRenderer.AddImage(api);
+    pdfRenderer.EndDocument();
+    TessModule._free(pdfRenderer);
+
+    const data = TessModule.FS.readFile(`/${tessjs_pdf_name}.pdf`);
+
+    if (tessjs_pdf_bin) {
+      files = { pdf: data, ...files };
+    }
+
+    if (tessjs_pdf_auto_download) {
+      adapter.writeFile(`${tessjs_pdf_name}.pdf`, data, 'application/pdf');
+    }
+  }
+
+  return files;
+};
diff --git a/src/common/workerUtils.js b/src/common/workerUtils.js
index 5d582d0..8a12660 100644
--- a/src/common/workerUtils.js
+++ b/src/common/workerUtils.js
@@ -7,13 +7,12 @@
  * @author Guillermo Webster <gui@mit.edu>
  * @author Jerome Wu <jeromewus@gmail.com>
  */
-const { readImage, loadLang } = require('tesseract.js-utils');
+const { loadLang } = require('tesseract.js-utils');
 const pdfTTF = require('./pdf-ttf');
 const dump = require('./dump');
-const { defaultParams } = require('./options');
 const { OEM, PSM } = require('./types');
-
-const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
+const { isBrowser } = require('./env');
+const { setImage, getLangsStr, getFiles } = require('./utils');
 
 /*
  * Tesseract Module returned by TesseractCore.
@@ -25,117 +24,8 @@ let TessModule;
 let api;
 let latestJob;
 let adapter = {};
+let curParams = {};
 
-/**
- * setImage
- *
- * @name setImage
- * @function set image in tesseract for recognition
- * @access public
- * @param {array} image - binary array in array format
- * @returns {number} - an emscripten pointer of the image
- */
-const setImage = (image, params) => {
-  const {
-    tessjs_image_rectangle_left: left,
-    tessjs_image_rectangle_top: top,
-    tessjs_image_rectangle_width: width,
-    tessjs_image_rectangle_height: height,
-  } = params;
-  const {
-    w, h, bytesPerPixel, data, pix,
-  } = readImage(TessModule, Array.from(image));
-
-  /*
-   * As some image format (ex. bmp) is not supported natiely by tesseract,
-   * sometimes it will not return pix directly, but data and bytesPerPixel
-   * for another SetImage usage.
-   *
-   */
-  if (data === null) {
-    api.SetImage(pix);
-  } else {
-    api.SetImage(data, w, h, bytesPerPixel, w * bytesPerPixel);
-  }
-  api.SetRectangle(
-    (left < 0) ? 0 : left,
-    (top < 0) ? 0 : top,
-    (width < 0) ? w : width,
-    (height < 0) ? h : height,
-  );
-  return data === null ? pix : data;
-};
-
-const getLangsStr = langs => (
-  typeof langs === 'string'
-    ? langs
-    : langs.map(lang => (typeof lang === 'string' ? lang : lang.data)).join('+')
-);
-
-/**
- * handleParams
- *
- * @name handleParams
- * @function hanlde params from users
- * @access private
- * @param {string} langs - lang string for Init()
- * @param {object} customParams - an object of params
- */
-const handleParams = (langs, iParams) => {
-  const {
-    tessedit_ocr_engine_mode,
-    ...params
-  } = iParams;
-  api.Init(null, getLangsStr(langs), tessedit_ocr_engine_mode);
-  Object.keys(params).forEach((key) => {
-    if (!key.startsWith('tessjs')) {
-      api.SetVariable(key, params[key]);
-    }
-  });
-};
-
-/**
- * handleOutput
- *
- * @name handleOutput
- * @function handle file output
- * @access private
- * @param {object} customParams - an object of params
- */
-const handleOutput = (customParams) => {
-  let files = {};
-  const {
-    tessjs_create_pdf,
-    tessjs_textonly_pdf,
-    tessjs_pdf_name,
-    tessjs_pdf_title,
-    tessjs_pdf_auto_download,
-    tessjs_pdf_bin,
-  } = {
-    ...defaultParams,
-    ...customParams,
-  };
-
-  if (tessjs_create_pdf === '1') {
-    const pdfRenderer = new TessModule.TessPDFRenderer(tessjs_pdf_name, '/', tessjs_textonly_pdf === '1');
-    pdfRenderer.BeginDocument(tessjs_pdf_title);
-    pdfRenderer.AddImage(api);
-    pdfRenderer.EndDocument();
-    TessModule._free(pdfRenderer);
-
-    const data = TessModule.FS.readFile(`/${tessjs_pdf_name}.pdf`);
-
-    if (tessjs_pdf_bin) {
-      files = { pdf: data, ...files };
-    }
-
-    if (tessjs_pdf_auto_download) {
-      adapter.writeFile(`${tessjs_pdf_name}.pdf`, data, 'application/pdf');
-    }
-  }
-
-  return files;
-};
 
 /**
  * handleInit
@@ -148,26 +38,32 @@ const handleOutput = (customParams) => {
  * @param {object} res - job instance
  * @returns {Promise} A Promise for callback
  */
-const handleInit = ({ corePath }, res) => {
+const load = ({ workerId, jobId, payload: { corePath } }, res) => {
   if (!TessModule) {
     const Core = adapter.getCore(corePath, res);
 
-    res.progress({ status: 'initializing tesseract', progress: 0 });
+    res.progress({ workerId, status: 'initializing tesseract', progress: 0 });
 
-    return Core({
+    Core({
       TesseractProgress(percent) {
-        latestJob.progress({ status: 'recognizing text', progress: Math.max(0, (percent - 30) / 70) });
+        latestJob.progress({
+          workerId,
+          jobId,
+          status: 'recognizing text',
+          progress: Math.max(0, (percent - 30) / 70),
+        });
       },
     })
       .then((tessModule) => {
         TessModule = tessModule;
         TessModule.FS.writeFile('/pdf.ttf', adapter.b64toU8Array(pdfTTF));
         api = new TessModule.TessBaseAPI();
-        res.progress({ status: 'initialized tesseract', progress: 1 });
+        res.progress({ workerId, status: 'initialized tesseract', progress: 1 });
+        res.resolve({ loaded: true });
       });
+  } else {
+    res.resolve({ loaded: true });
   }
-
-  return Promise.resolve();
 };
 
 /**
@@ -182,14 +78,59 @@ const handleInit = ({ corePath }, res) => {
  * @param {object} res - job instance
  * @returns {Promise} A Promise for callback
  */
-const loadLanguage = ({ langs, options }, res) => {
-  res.progress({ status: 'loading language traineddata', progress: 0 });
-  return loadLang({ langs, TessModule, ...options }).then((...args) => {
-    res.progress({ status: 'loaded language traineddata', progress: 1 });
-    return args;
+const loadLanguage = ({ workerId, payload: { langs, options } }, res) => {
+  res.progress({ workerId, status: 'loading language traineddata', progress: 0 });
+  loadLang({ langs, TessModule, ...options }).then(() => {
+    res.progress({ workerId, status: 'loaded language traineddata', progress: 1 });
+    res.resolve(langs);
+  }).catch((e) => {
+    if (isBrowser && e instanceof DOMException) {
+      /*
+       * For some reason google chrome throw DOMException in loadLang,
+       * while other browser is OK, for now we ignore this exception
+       * and hopefully to find the root cause one day.
+       */
+    } else {
+      res.reject(e.toString());
+    }
   });
 };
 
+const initialize = ({
+  workerId,
+  jobId,
+  payload: { langs, params },
+}, res) => {
+  let { tessedit_ocr_engine_mode: oem } = params;
+  let l = langs;
+
+  res.progress({
+    workerId, jobId, status: 'initializing api', progress: 0,
+  });
+  if ([
+    PSM.OSD_ONLY,
+    PSM.AUTO_OSD,
+    PSM.RAW_LINE,
+  ].includes(params.tessedit_pageseg_mode)) {
+    l = (typeof l === 'string') ? `${l}+osd` : [...l, 'osd'];
+    // oem = OEM.TESSERACT_ONLY;
+  }
+  api.Init(null, getLangsStr(l), oem);
+  Object.keys(params).forEach((key) => {
+    if (!key.startsWith('tessjs')) {
+      api.SetVariable(key, params[key]);
+    }
+  });
+  curParams = {
+    tessedit_ocr_engine_mode: oem,
+    ...params,
+  };
+  res.progress({
+    workerId, jobId, status: 'initialized api', progress: 1,
+  });
+  res.resolve();
+};
+
 /**
  * handleRecognize
  *
@@ -203,66 +144,18 @@ const loadLanguage = ({ langs, options }, res) => {
  * @param {object} req.params - parameters for tesseract
  * @param {object} res - job instance
  */
-const handleRecognize = ({
-  image, langs: iLangs, options, params: customParams,
-}, res) => {
-  const params = {
-    ...defaultParams,
-    ...customParams,
-  };
-  const { tessedit_pageseg_mode } = params;
-  let langs = iLangs;
-
-  /*
-   * When PSM === OSD_ONLY or AUTO_OSD or RAW_LINE
-   * osd.traineddata must be included and
-   * OEM must be TESSERACT_ONLY (LSTM doesn't support OSD)
-   */
-  if ([
-    PSM.OSD_ONLY,
-    PSM.AUTO_OSD,
-    PSM.RAW_LINE,
-  ].includes(tessedit_pageseg_mode)) {
-    langs = (typeof iLangs === 'string')
-      ? `${iLangs}+osd`
-      : [...iLangs, 'osd'];
-    params.tessedit_ocr_engine_mode = OEM.TESSERACT_ONLY;
+const recognize = ({ payload: { image } }, res) => {
+  try {
+    const ptr = setImage(TessModule, api, image, curParams);
+    api.Recognize(null);
+    res.resolve({
+      files: getFiles(TessModule, api, adapter, curParams),
+      ...dump(TessModule, api, curParams),
+    });
+    TessModule._free(ptr);
+  } catch (err) {
+    res.reject(err.toString());
   }
-  return handleInit(options, res)
-    .then(() => (
-      loadLanguage({ langs, params, options }, res)
-        .catch((e) => {
-          if (isBrowser && e instanceof DOMException) {
-            /*
-             * For some reason google chrome throw DOMException in loadLang,
-             * while other browser is OK, for now we ignore this exception
-             * and hopefully to find the root cause one day.
-             */
-          } else {
-            throw e;
-          }
-        })
-        .then(() => {
-          try {
-            const progressUpdate = (progress) => {
-              res.progress({ status: 'initializing api', progress });
-            };
-            progressUpdate(0);
-            handleParams(langs, params);
-            progressUpdate(0.5);
-            const ptr = setImage(image, params);
-            progressUpdate(1);
-            api.Recognize(null);
-            const files = handleOutput(params);
-            const result = dump(TessModule, api, params);
-            api.End();
-            TessModule._free(ptr);
-            res.resolve({ files, ...result });
-          } catch (err) {
-            res.reject({ err });
-          }
-        })
-    ));
 };
 
 /**
@@ -277,46 +170,34 @@ const handleRecognize = ({
  * @param {object} req.options - other options for loadLang function
  * @param {object} res - job instance
  */
-const handleDetect = ({
-  image, langs, options, params: customParams,
-}, res) => (
-  handleInit(options, res)
-    .then(() => (
-      loadLanguage({ langs, options }, res)
-        .then(() => {
-          api.Init(null, getLangsStr(langs), OEM.TESSERACT_ONLY);
-          api.SetPageSegMode(PSM.OSD_ONLY);
-          const params = {
-            ...defaultParams,
-            ...customParams,
-          };
-
-          const ptr = setImage(image, params);
-          const results = new TessModule.OSResults();
+const detect = ({ payload: { image } }, res) => {
+  try {
+    const ptr = setImage(TessModule, api, image, curParams);
+    const results = new TessModule.OSResults();
 
-          if (!api.DetectOS(results)) {
-            api.End();
-            TessModule._free(ptr);
-            res.reject('Failed to detect OS');
-          } else {
-            const best = results.best_result;
-            const oid = best.orientation_id;
-            const sid = best.script_id;
+    if (!api.DetectOS(results)) {
+      api.End();
+      TessModule._free(ptr);
+      res.reject('Failed to detect OS');
+    } else {
+      const best = results.best_result;
+      const oid = best.orientation_id;
+      const sid = best.script_id;
 
-            api.End();
-            TessModule._free(ptr);
+      TessModule._free(ptr);
 
-            res.resolve({
-              tesseract_script_id: sid,
-              script: results.unicharset.get_script_from_script_id(sid),
-              script_confidence: best.sconfidence,
-              orientation_degrees: [0, 270, 180, 90][oid],
-              orientation_confidence: best.oconfidence,
-            });
-          }
-        })
-    ))
-);
+      res.resolve({
+        tesseract_script_id: sid,
+        script: results.unicharset.get_script_from_script_id(sid),
+        script_confidence: best.sconfidence,
+        orientation_degrees: [0, 270, 180, 90][oid],
+        orientation_confidence: best.oconfidence,
+      });
+    }
+  } catch (err) {
+    res.reject(err.toString());
+  }
+};
 
 /**
  * dispatchHandlers
@@ -330,12 +211,11 @@ const handleDetect = ({
  * @param {object} data.payload - data for the job
  * @param {function} send - trigger job to work
  */
-exports.dispatchHandlers = ({ jobId, action, payload }, send) => {
+exports.dispatchHandlers = (packet, send) => {
   const res = (status, data) => {
     send({
-      jobId,
+      ...packet,
       status,
-      action,
       data,
     });
   };
@@ -346,10 +226,17 @@ exports.dispatchHandlers = ({ jobId, action, payload }, send) => {
   latestJob = res;
 
   try {
-    if (action === 'recognize') {
-      handleRecognize(payload, res);
+    const { action } = packet;
+    if (action === 'load') {
+      load(packet, res);
+    } else if (action === 'load-language') {
+      loadLanguage(packet, res);
+    } else if (action === 'initialize') {
+      initialize(packet, res);
+    } else if (action === 'recognize') {
+      recognize(packet, res);
     } else if (action === 'detect') {
-      handleDetect(payload, res);
+      detect(packet, res);
     }
   } catch (err) {
     /** Prepare exception to travel through postMessage */
diff --git a/src/index.js b/src/index.js
index 5dc211b..ffda1db 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,6 +9,9 @@
  */
 const utils = require('tesseract.js-utils');
 const TesseractWorker = require('./common/TesseractWorker');
+const createScheduler = require('./common/createScheduler');
+const createWorker = require('./common/createWorker');
+const createJob = require('./common/createJob');
 const types = require('./common/types');
 
 module.exports = {
@@ -18,4 +21,7 @@ module.exports = {
   utils,
   /** Check ./common/types for more details */
   ...types,
+  createScheduler,
+  createWorker,
+  createJob,
 };
diff --git a/src/node/index.js b/src/node/index.js
index 54d87a1..8103691 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -67,12 +67,12 @@ exports.defaultOptions = {
  * @param {object} options
  * @param {string} options.workerPath - worker script path
  */
-exports.spawnWorker = (instance, { workerPath }) => {
-  const cp = fork(workerPath);
-  cp.on('message', (packet) => {
-    instance.recv(packet);
-  });
-  return cp;
+exports.spawnWorker = ({ workerPath }) => (
+  fork(workerPath)
+);
+
+exports.setOnMessage = (worker, handler) => {
+  worker.on('message', handler);
 };
 
 /**
@@ -83,8 +83,8 @@ exports.spawnWorker = (instance, { workerPath }) => {
  * @access public
  * @param {object} instance TesseractWorker instance
  */
-exports.terminateWorker = (instance) => {
-  instance.worker.kill();
+exports.terminateWorker = ({ worker }) => {
+  worker.kill();
 };
 
 /**
@@ -96,12 +96,16 @@ exports.terminateWorker = (instance) => {
  * @param {object} instance TesseractWorker instance
  * @param {object} iPacket data for worker
  */
-exports.sendPacket = (instance, iPacket) => {
-  const packet = { ...iPacket };
-  loadImage(packet.payload.image)
-    .then(buf => new Uint8Array(buf))
-    .then((img) => {
-      packet.payload.image = Array.from(img);
-      instance.worker.send(packet);
-    });
+exports.sendPacket = ({ worker }, packet) => {
+  const p = { ...packet };
+  if (['recognize', 'detect'].includes(p.action)) {
+    loadImage(p.payload.image)
+      .then(buf => new Uint8Array(buf))
+      .then((img) => {
+        p.payload.image = Array.from(img);
+        worker.send(p);
+      });
+  } else {
+    worker.send(p);
+  }
 };