{"id":14977783,"url":"https://github.com/veliovgroup/josk","last_synced_at":"2025-10-12T00:13:20.460Z","repository":{"id":15100337,"uuid":"77552941","full_name":"veliovgroup/josk","owner":"veliovgroup","description":"Node.js setInterval and CRON tasks scheduler and manager for horizontally scaled multi-server applications","archived":false,"fork":false,"pushed_at":"2025-09-26T19:49:04.000Z","size":2993,"stargazers_count":38,"open_issues_count":3,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-27T12:23:55.196Z","etag":null,"topics":["cluster","cron","horizontal-scaling","jobs","mongodb","node-js","nodejs","npm","npm-package","redis","scheduled-jobs","scheduler","task","task-manager","task-scheduler","tasks"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/josk","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/veliovgroup.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"dr-dimitru","custom":"https://paypal.me/veliovgroup"}},"created_at":"2016-12-28T18:25:58.000Z","updated_at":"2025-07-22T18:59:40.000Z","dependencies_parsed_at":"2024-04-17T11:47:08.511Z","dependency_job_id":"f9ae35da-8de0-488b-9946-ce41d55efa73","html_url":"https://github.com/veliovgroup/josk","commit_stats":{"total_commits":208,"total_committers":3,"mean_commits":69.33333333333333,"dds":0.05288461538461542,"last_synced_commit":"ad2d4d35a7a43dc389428f804a8efd925ed574e0"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/veliovgroup/josk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veliovgroup%2Fjosk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veliovgroup%2Fjosk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veliovgroup%2Fjosk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veliovgroup%2Fjosk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/veliovgroup","download_url":"https://codeload.github.com/veliovgroup/josk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veliovgroup%2Fjosk/sbom","scorecard":{"id":147548,"data":{"date":"2022-08-15","repo":{"name":"github.com/veliovgroup/josk","commit":"a77c44aad18dfe4148857198a81be85834751017"},"scorecard":{"version":"v4.5.0-28-g6dcfde9","commit":"6dcfde9299033d3b16d81985a9634092411e0cfc"},"score":6.3,"checks":[{"name":"Code-Review","score":1,"reason":"GitHub code reviews found for 4 commits out of the last 30 -- score normalized to 1","details":["Warn: no reviews found for commit: a77c44aad18dfe4148857198a81be85834751017","Warn: no reviews found for commit: d4bafd4d0e10b7a990c748e9b7811bdc16614aad","Warn: no reviews found for commit: 4329acbe9c365720ce7a8b8a2497a08f27347447","Warn: no reviews found for commit: da309438b414776ae015355e3a6f8ce6b6251472","Warn: no reviews found for commit: 88c4ce4c9a19caf529ba1867264643a55264ac64","Warn: no reviews found for commit: 732857bd5732bde1e0180e02ee32f68b9cf4f7bc","Warn: no reviews found for commit: 364da5fb38a832211cdb1e4be2774d608857a93c","Warn: no reviews found for commit: 51ff340c9a0a9bd7b0b922c6730a02e454d21808","Warn: no reviews found for commit: 14ac441fea7f01f46c2109af49c9c1ce9976e02d","Warn: no reviews found for commit: 3fddf2e716934e56c6188b80c023ac38160b3b20","Warn: no reviews found for commit: fcb9ef589fa81c12c5901fc526af2557e56ed976","Warn: no reviews found for commit: fa2d19701880c1e31a633aaaf4fa07c7ab348756","Warn: no reviews found for commit: c99958408b4b75a1cf75f1725966cb46a364f636","Warn: no reviews found for commit: 42dc2bba76d254fa32156c4e0f778f629233463a","Warn: no reviews found for commit: c6b84c36fde82b8127faaf25a874715c73186dcd","Warn: no reviews found for commit: cd626615a9549b0cd759dfaf8c9d00a3855fda45","Warn: no reviews found for commit: 83584187c9f0d52814f99e1daa484a2ba763b164","Warn: no reviews found for commit: 31c5628af06d7f3fe929f7433c408cbe808205c6","Warn: no reviews found for commit: 464a7adc4f5be4d0da3069d1948154ef3c54aa45","Warn: no reviews found for commit: 3f2a94a58d1f2b42a4f6f47e26f29cf66181ddb0","Warn: no reviews found for commit: 46f3b425770317c66649f1a4c708b05e8c5c5c67","Warn: no reviews found for commit: f59fd1f138efb164a9781325b7f84a3eb153c733","Warn: no reviews found for commit: 7c0ac5aec270d475c3b94e9a461c2e8f1c6adcf3","Warn: no reviews found for commit: e245a91c52d0d770cf1da4e320723b947d6f87a9","Warn: no reviews found for commit: 3f637d2af6d7a6f674a3c2ff0fb3ab038fa65585","Warn: no reviews found for commit: a9bf85b2adf98b341502e7ba488153f85eee2463"],"documentation":{"short":"Determines if the project requires code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#code-review"}},{"name":"Maintained","score":5,"reason":"6 commit(s) out of 30 and 0 issue activity out of 3 found in the last 90 days -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no badge detected","details":null,"documentation":{"short":"Determines if the project has a CII Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"no vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#vulnerabilities"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":10,"reason":"tokens are read-only in GitHub workflows","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"no published package detected","details":["Warn: no GitHub publishing workflow detected"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":10,"reason":"all dependencies are pinned","details":["Info: GitHub-owned GitHubActions are pinned","Info: Third-party GitHubActions are pinned","Info: Dockerfile dependencies are pinned","Info: no insecure (not pinned by hash) dependency downloads found in Dockerfiles","Info: no insecure (not pinned by hash) dependency downloads found in shell scripts"],"documentation":{"short":"Determines if the project has declared and pinned its dependencies.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#binary-artifacts"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: : LICENSE:1"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":["Warn: no GitHub releases found"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#branch-protection"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: Dependabot detected"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#dependency-update-tool"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":null,"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":null,"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/6dcfde9299033d3b16d81985a9634092411e0cfc/docs/checks.md#security-policy"}}]},"last_synced_at":"2025-08-16T09:41:53.433Z","repository_id":15100337,"created_at":"2025-08-16T09:41:53.433Z","updated_at":"2025-08-16T09:41:53.433Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279009475,"owners_count":26084609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster","cron","horizontal-scaling","jobs","mongodb","node-js","nodejs","npm","npm-package","redis","scheduled-jobs","scheduler","task","task-manager","task-scheduler","tasks"],"created_at":"2024-09-24T13:56:19.936Z","updated_at":"2025-10-12T00:13:20.443Z","avatar_url":"https://github.com/veliovgroup.png","language":"JavaScript","funding_links":["https://github.com/sponsors/dr-dimitru","https://paypal.me/veliovgroup"],"categories":[],"sub_categories":[],"readme":"[![support](https://img.shields.io/badge/support-GitHub-white)](https://github.com/sponsors/dr-dimitru)\n[![support](https://img.shields.io/badge/support-PayPal-white)](https://paypal.me/veliovgroup)\n\u003ca href=\"https://ostr.io/info/built-by-developers-for-developers?ref=github-josk-repo-top\"\u003e\u003cimg src=\"https://ostr.io/apple-touch-icon-60x60.png\" height=\"20\"\u003e\u003c/a\u003e\n\u003ca href=\"https://meteor-files.com/?ref=github-josk-repo-top\"\u003e\u003cimg src=\"https://meteor-files.com/apple-touch-icon-60x60.png\" height=\"20\"\u003e\u003c/a\u003e\n\n# JoSk\n\n\"JoSk\" is a Node.js task manager for horizontally scaled apps and apps that would need to scale horizontally quickly at some point of growth.\n\n\"JoSk\" mimics the native API of `setTimeout` and `setInterval` and supports [CRON expressions](https://github.com/veliovgroup/josk?tab=readme-ov-file#cron). All queued tasks are synced between all running application instances via Redis, MongoDB, or a [custom adapter](https://github.com/veliovgroup/josk/blob/master/docs/adapter-api.md).\n\nThe \"JoSk\" package is made for a variety of horizontally scaled apps, such as clusters, multi-servers, and multi-threaded Node.js instances, that are running either on the same or different machines or even different data centers. \"JoSk ensures that the only single execution of each task occurs across all running instances of the application.\n\n\"JoSk\" is not just for multi-instance apps. It seamlessly integrates with single-instance applications as well, showcasing its versatility and adaptability.\n\n__Note: JoSk is the server-only package.__\n\n## ToC\n\n- [Main features](https://github.com/veliovgroup/josk?tab=readme-ov-file#main-features)\n- [Prerequisites](https://github.com/veliovgroup/josk?tab=readme-ov-file#prerequisites)\n- [Install](https://github.com/veliovgroup/josk?tab=readme-ov-file#install) as [NPM package](https://www.npmjs.com/package/josk)\n- [API](https://github.com/veliovgroup/josk?tab=readme-ov-file#api)\n  - [Constructor `new JoSk()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#initialization)\n    - [`RedisAdapter`](https://github.com/veliovgroup/josk?tab=readme-ov-file#redis-adapter)\n    - [`MongoAdapter`](https://github.com/veliovgroup/josk?tab=readme-ov-file#mongodb-adapter)\n  - [`JoSk#setInterval()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#setintervalfunc-delay-uid)\n  - [`JoSk#setTimeout()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#settimeoutfunc-delay-uid)\n  - [`JoSk#setImmediate()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#setimmediatefunc-uid)\n  - [`JoSk#clearInterval()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#clearintervaltimer)\n  - [`JoSk#clearTimeout()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#cleartimeouttimer)\n  - [`JoSk#destroy()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#destroy)\n  - [`JoSk#ping()`](https://github.com/veliovgroup/josk?tab=readme-ov-file#ping)\n- [Examples](https://github.com/veliovgroup/josk?tab=readme-ov-file#examples)\n  - [CRON usage](https://github.com/veliovgroup/josk?tab=readme-ov-file#cron)\n  - [Passing arguments](https://github.com/veliovgroup/josk?tab=readme-ov-file#pass-arguments)\n  - [Clean up stale tasks](https://github.com/veliovgroup/josk?tab=readme-ov-file#clean-up-old-tasks)\n  - [MongoDB connection options](https://github.com/veliovgroup/josk?tab=readme-ov-file#mongodb-connection-fine-tuning)\n  - [Meteor.js](https://github.com/veliovgroup/josk/blob/master/docs/meteor.md)\n- [Important notes](https://github.com/veliovgroup/josk?tab=readme-ov-file#notes)\n- [~99% tests coverage](https://github.com/veliovgroup/josk?tab=readme-ov-file#running-tests)\n- [Why it's named \"JoSk\"](https://github.com/veliovgroup/josk?tab=readme-ov-file#why-josk)\n- [Support Section](https://github.com/veliovgroup/josk?tab=readme-ov-file#support-our-open-source-contribution)\n\n## Main features\n\n- 🏢 Synchronize single task across multiple servers;\n- 🔏 Read locking to avoid simultaneous task executions across complex infrastructure;\n- 📦 Zero dependencies, written from scratch for top performance;\n- 👨‍🔬 ~99% tests coverage;\n- 💪 Bulletproof design, built-in retries, and \"zombie\" task recovery 🧟🔫.\n\n## Prerequisites\n\n- `redis-server@\u003e=5.0.0` — Redis Server Version (*if used with RedisAdapter*)\n- `mongod@\u003e=4.0.0` — MongoDB Server Version (*if used with MongoAdapter*)\n- `node@\u003e=14.20.0` — Node.js version\n\n### Older releases compatibility\n\n- `mongod@\u003c4.0.0` — use `josk@=1.1.0`\n- `node@\u003c14.20.0` — use `josk@=3.0.2`\n- `node@\u003c8.9.0` — use `josk@=1.1.0`\n\n## Install:\n\n```shell\nnpm install josk --save\n```\n\n```js\n// ES Module Style\nimport { JoSk, RedisAdapter, MongoAdapter } from 'josk';\n\n// CommonJS\nconst { JoSk, RedisAdapter, MongoAdapter } = require('josk');\n```\n\n## API:\n\nConstructor options for *JoSK*, *MongoAdapter*, and *RedisAdapter*\n\n### `new JoSk(opts)`\n\n- `opts.adapter` {*RedisAdapter*|*MongoAdapter*} - [Required] Instance of `RedisAdapter` or `MongoAdapter` or [custom adapter](https://github.com/veliovgroup/josk/blob/master/docs/adapter-api.md)\n- `opts.debug` {*Boolean*} - [Optional] Enable debugging messages, useful during development\n- `opts.autoClear` {*Boolean*} - [Optional] Remove (*Clear*) obsolete tasks (*any tasks which are not found in the instance memory (runtime), but exists in the database*). Obsolete tasks may appear in cases when it wasn't cleared from the database on process shutdown, and/or was removed/renamed in the app. Obsolete tasks may appear if multiple app instances running different codebase within the same database, and the task may not exist on one of the instances. Default: `false`\n- `opts.zombieTime` {*Number*} - [Optional] time in milliseconds, after this time - task will be interpreted as \"*zombie*\". This parameter allows to rescue task from \"*zombie* mode\" in case when: `ready()` wasn't called, exception during runtime was thrown, or caused by bad logic. While `resetOnInit` option helps to make sure tasks are `done` on startup, `zombieTime` option helps to solve same issue, but during runtime. Default value is `900000` (*15 minutes*). It's not recommended to set this value to below `60000` (*one minute*)\n- `opts.minRevolvingDelay` {*Number*} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in milliseconds. Default: `128`\n- `opts.maxRevolvingDelay` {*Number*} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in milliseconds. Default: `768`\n- `opts.onError` {*Function*} - [Optional] Informational hook, called instead of throwing exceptions. Default: `false`. Called with two arguments:\n  - `title` {*String*}\n  - `details` {*Object*}\n  - `details.description` {*String*}\n  - `details.error` {*Mix*}\n  - `details.uid` {*String*} - Internal `uid`, suitable for `.clearInterval()` and `.clearTimeout()`\n- `opts.onExecuted` {*Function*} - [Optional] Informational hook, called when task is finished. Default: `false`. Called with two arguments:\n  - `uid` {*String*} - `uid` passed into `.setImmediate()`, `.setTimeout()`, or `setInterval()` methods\n  - `details` {*Object*}\n  - `details.uid` {*String*} - Internal `uid`, suitable for `.clearInterval()` and `.clearTimeout()`\n  - `details.date` {*Date*} - Execution timestamp as JS {*Date*}\n  - `details.delay` {*Number*} - Execution `delay` (e.g. `interval` for `.setInterval()`)\n  - `details.timestamp` {*Number*} - Execution timestamp as unix {*Number*}\n\n### `new RedisAdapter(opts)`\n\n*Since* `v5.0.0`\n\n- `opts.client` {*RedisClient*} - [*Required*] `RedisClient` instance, like one returned from `await redis.createClient().connect()` method\n- `opts.prefix` {*String*} - [Optional] use to create multiple named instances\n- `opts.resetOnInit` {*Boolean*} - [Optional] (*__use with caution__*) make sure all old tasks are completed during initialization. Useful for single-instance apps to clean up unfinished that occurred due to intermediate shutdown, reboot, or exception. Default: `false`\n\n### `new MongoAdapter(opts)`\n\n*Since* `v5.0.0`\n\n- `opts.db` {*Db*} - [*Required*] Mongo's `Db` instance, like one returned from `MongoClient#db()` method\n- `opts.prefix` {*String*} - [Optional] use to create multiple named instances\n- `opts.lockCollectionName` {*String*} - [Optional] By default all JoSk instances use the same `__JobTasks__.lock` collection for locking\n- `opts.resetOnInit` {*Boolean*} - [Optional] (*__use with caution__*) make sure all old tasks are completed during initialization. Useful for single-instance apps to clean up unfinished that occurred due to intermediate shutdown, reboot, or exception. Default: `false`\n\n### Initialization\n\nJoSk is storage-agnostic (since `v4.0.0`). It's shipped with Redis and MongoDB \"adapters\" out of the box, with option to extend its capabilities by creating and passing a [custom adapter](https://github.com/veliovgroup/josk/blob/master/docs/adapter-api.md)\n\n#### Redis Adapter\n\nJoSk has no dependencies, hence make sure `redis` NPM package is installed in order to support Redis Storage Adapter. `RedisAdapter` utilize basic set of commands `SET`, `GET`, `DEL`, `EXISTS`, `HSET`, `HGETALL`, and `SCAN`. `RedisAdapter` is compatible with all Redis-alike databases, and was well-tested with [Redis](https://redis.io/) and [KeyDB](https://docs.keydb.dev/)\n\n```js\nimport { JoSk, RedisAdapter } from 'josk';\nimport { createClient } from 'redis';\n\nconst redisClient = await createClient({\n  url: 'redis://127.0.0.1:6379'\n}).connect();\n\nconst jobs = new JoSk({\n  adapter: new RedisAdapter({\n    client: redisClient,\n    prefix: 'app-scheduler',\n  }),\n  onError(reason, details) {\n    // Use onError hook to catch runtime exceptions\n    // thrown inside scheduled tasks\n    console.log(reason, details.error);\n  }\n});\n```\n\n#### MongoDB Adapter\n\nJoSk has no dependencies, hence make sure `mongodb` NPM package is installed in order to support MongoDB Storage Adapter. Note: this package will add two new MongoDB collections per each `new JoSk()`. One collection for tasks and second for \"Read Locking\" with `.lock` suffix\n\n```js\nimport { JoSk, MongoAdapter } from 'josk';\nimport { MongoClient } from 'mongodb';\n\nconst client = new MongoClient('mongodb://127.0.0.1:27017');\n// To avoid \"DB locks\" — it's a good idea to use separate DB from the \"main\" DB\nconst mongoDb = client.db('joskdb');\nconst jobs = new JoSk({\n  adapter: new MongoAdapter({\n    db: mongoDb,\n    prefix: 'cluster-scheduler',\n  }),\n  onError(reason, details) {\n    // Use onError hook to catch runtime exceptions\n    // thrown inside scheduled tasks\n    console.log(reason, details.error);\n  }\n});\n```\n\n#### Create the first task\n\nAfter JoSk initialized simply call `JoSk#setInterval` to create recurring task\n\n```js\nconst jobs = new JoSk({ /*...*/ });\n\njobs.setInterval((ready) =\u003e {\n  /* ...code here... */\n  ready();\n}, 60 * 60000, 'task1h'); // every hour\n\njobs.setInterval((ready) =\u003e {\n  /* ...code here... */\n  asyncCall(() =\u003e {\n    /* ...more code here...*/\n    ready();\n  });\n}, 15 * 60000, 'asyncTask15m'); // every 15 mins\n\n/**\n * no need to call ready() inside async function\n */\njobs.setInterval(async () =\u003e {\n  try {\n    await asyncMethod();\n  } catch (err) {\n    console.log(err)\n  }\n}, 30 * 60000, 'asyncAwaitTask30m'); // every 30 mins\n\n/**\n * no need to call ready() when call returns Promise\n */\njobs.setInterval(() =\u003e {\n  return asyncMethod(); // \u003c-- returns Promise\n}, 2 * 60 * 60000, 'asyncAwaitTask2h'); // every two hours\n```\n\nNote: This library relies on job ID. Always use different `uid`, even for the same task:\n\n```js\nconst task = function (ready) {\n  //... code here\n  ready();\n};\n\njobs.setInterval(task, 60000, 'task-1m'); // every minute\njobs.setInterval(task, 2 * 60000, 'task-2m'); // every two minutes\n```\n\n### `setInterval(func, delay, uid)`\n\n- `func` {*Function*} - Function to call on schedule\n- `delay` {*Number*} - Delay for the first run and interval between further executions in milliseconds\n- `uid` {*String*} - Unique app-wide task id\n- Returns: {*`Promise\u003cstring\u003e`*}\n\n*Set task into interval execution loop.* `ready()` *callback is passed as the first argument into a task function.*\n\nIn the example below, the next task __will not be scheduled__ until the current is ready:\n\n```js\njobs.setInterval(function (ready) {\n  /* ...run sync code... */\n  ready();\n}, 60 * 60000, 'syncTask1h'); // will execute every hour + time to execute the task\n\njobs.setInterval(async function () {\n  try {\n    await asyncMethod();\n  } catch (err) {\n    console.log(err)\n  }\n}, 60 * 60000, 'asyncAwaitTask1h'); // will execute every hour + time to execute the task\n```\n\nIn the example below, the next task __will not wait__ for the current task to finish:\n\n```js\njobs.setInterval(function (ready) {\n  ready();\n  /* ...run sync code... */\n}, 60 * 60000, 'syncTask1h'); // will execute every hour\n\njobs.setInterval(async function () {\n  /* ...task re-scheduled instantly here... */\n  process.nextTick(async () =\u003e {\n    await asyncMethod();\n  });\n}, 60 * 60000, 'asyncAwaitTask1h'); // will execute every hour\n```\n\nIn the next example, a long running task is executed in a loop without delay after the full execution:\n\n```js\njobs.setInterval(function (ready) {\n  asyncCall((error, result) =\u003e {\n    if (error) {\n      ready(); // \u003c-- Always run `ready()`, even if call was unsuccessful\n    } else {\n      anotherCall(result.data, ['param'], (error, response) =\u003e {\n        if (error) {\n          ready(); // \u003c-- Always run `ready()`, even if call was unsuccessful\n          return;\n        }\n\n        waitForSomethingElse(response, () =\u003e {\n          ready(); // \u003c-- End of the full execution\n        });\n      });\n    }\n  });\n}, 0, 'longRunningAsyncTask'); // run in a loop as soon as previous run is finished\n```\n\nSame task combining `await`/`async` and callbacks\n\n```js\njobs.setInterval(function (ready) {\n  process.nextTick(async () =\u003e {\n    try {\n      const result = await asyncCall();\n      const response = await anotherCall(result.data, ['param']);\n\n      waitForSomethingElse(response, () =\u003e {\n        ready(); // \u003c-- End of the full execution\n      });\n    } catch (err) {\n      console.log(err)\n      ready(); // \u003c-- Always run `ready()`, even if call was unsuccessful\n    }\n  });\n}, 0, 'longRunningAsyncTask'); // run in a loop as soon as previous run is finished\n```\n\n### `setTimeout(func, delay, uid)`\n\n- `func` {*Function*} - Function to call after `delay`\n- `delay` {*Number*} - Delay in milliseconds\n- `uid` {*String*} - Unique app-wide task id\n- Returns: {*`Promise\u003cstring\u003e`*}\n\n*Run a task after delay in ms.* `setTimeout` *is useful for cluster - when you need to make sure task executed only once.* `ready()` *callback is passed as the first argument into a task function.*\n\n```js\njobs.setTimeout(function (ready) {\n  /* ...run sync code... */\n  ready();\n}, 60000, 'syncTaskIn1m'); // will run only once across the cluster in a minute\n\njobs.setTimeout(function (ready) {\n  asyncCall(function () {\n    /* ...run async code... */\n    ready();\n  });\n}, 60000, 'asyncTaskIn1m'); // will run only once across the cluster in a minute\n\njobs.setTimeout(async function () {\n  try {\n    /* ...code here... */\n    await asyncMethod();\n    /* ...more code here...*/\n  } catch (err) {\n    console.log(err)\n  }\n}, 60000, 'asyncAwaitTaskIn1m'); // will run only once across the cluster in a minute\n```\n\n### `setImmediate(func, uid)`\n\n- `func` {*Function*} - Function to execute\n- `uid`  {*String*}   - Unique app-wide task id\n- Returns: {*`Promise\u003cstring\u003e`*}\n\n*Immediate execute the function, and only once.* `setImmediate` *is useful for cluster - when you need to execute function immediately and only once across all servers.* `ready()` *is passed as the first argument into the task function.*\n\n```js\njobs.setImmediate(function (ready) {\n  //...run sync code\n  ready();\n}, 'syncTask'); // will run immediately and only once across the cluster\n\njobs.setImmediate(function (ready) {\n  asyncCall(function () {\n    //...run more async code\n    ready();\n  });\n}, 'asyncTask'); // will run immediately and only once across the cluster\n\njobs.setImmediate(async function () {\n  try {\n    /* ...code here... */\n    await asyncMethod();\n  } catch (err) {\n    console.log(err)\n  }\n}, 'asyncTask'); // will run immediately and only once across the cluster\n```\n\n### `clearInterval(timerId)`\n\n- `timerId` {*String*|*`Promise\u003cstring\u003e`*} — Timer id returned from `JoSk#setInterval()` method\n- Returns: {`Promise\u003cboolean\u003e`} `true` when task is successfully cleared, or `false` when task was not found\n\n*Cancel current interval timer.*\n\n```js\nconst timer = await jobs.setInterval(func, 34789, 'unique-taskid');\nawait jobs.clearInterval(timer);\n```\n\n### `clearTimeout(timerId)`\n\n- `timerId` {*String*|*`Promise\u003cstring\u003e`*} — Timer id returned from `JoSk#setTimeout()` method\n- Returns: {`Promise\u003cboolean\u003e`} `true` when task is successfully cleared, or `false` when task was not found\n\n*Cancel current timeout timer.*\n\n```js\nconst timer = await jobs.setTimeout(func, 34789, 'unique-taskid');\nawait jobs.clearTimeout(timer);\n```\n\n### `destroy()`\n\n- Returns: {*boolean*} `true` if instance successfully destroyed, `false` if instance already destroyed\n\n*Destroy JoSk instance*. This method shouldn't be called in normal circumstances. Stop internal interval timer. After JoSk is destroyed — calling public methods would end up logged to `stdout` or if `onError` hook was passed to JoSk it would receive an error. Only permitted methods are `clearTimeout` and `clearInterval`.\n\n```js\n// EXAMPLE: DESTROY JoSk INSTANCE UPON SERVER PROCESS TERMINATION\nconst jobs = new JoSk({ /* ... */ });\n\nconst cleanUpBeforeTermination = function () {\n  /* ...CLEAN UP AND STOP OTHER THINGS HERE... */\n  jobs.destroy();\n  process.exit(1);\n};\n\nprocess.stdin.resume();\nprocess.on('uncaughtException', cleanUpBeforeTermination);\nprocess.on('exit', cleanUpBeforeTermination);\nprocess.on('SIGHUP', cleanUpBeforeTermination);\n```\n\n### `ping()`\n\n- Returns: {*`Promise\u003cobject\u003e`*}\n\n*Ping JoSk instance*. Check scheduler readiness and its connection to the \"storage adapter\"\n\n```js\nconst jobs = new JoSk({ /* ... */ });\n\nconst pingResult = await jobs.ping();\nconsole.log(pingResult)\n/**\nIn case of the successful response\n{\n  status: 'OK',\n  code: 200,\n  statusCode: 200,\n}\n\nFailed response\n{\n  status: 'Error reason',\n  code: 500,\n  statusCode: 500,\n  error: ErrorObject\n}\n*/\n```\n\n## Examples\n\nUse cases and usage examples\n\n### CRON\n\nUse JoSk to invoke synchronized tasks by CRON schedule, and [`cron-parser` package](https://www.npmjs.com/package/cron-parser) to parse CRON expressions. To simplify CRON scheduling — grab and use `setCron` function below:\n\n```js\nimport parser from 'cron-parser';\n\nconst jobsCron = new JoSk({\n  adapter: new RedisAdapter({\n    client: await createClient({ url: 'redis://127.0.0.1:6379' }).connect(),\n    prefix: 'cron-scheduler'\n  }),\n  minRevolvingDelay: 512, // Adjust revolving delays to higher values\n  maxRevolvingDelay: 1000, // as CRON schedule defined to seconds\n});\n\n// CRON HELPER FUNCTION\nconst setCron = async (uniqueName, cronTask, task) =\u003e {\n  const nextTimestamp = +parser.parseExpression(cronTask).next().toDate();\n\n  return await jobsCron.setInterval(function (ready) {\n    ready(parser.parseExpression(cronTask).next().toDate());\n    task();\n  }, nextTimestamp - Date.now(), uniqueName);\n};\n\nsetCron('Run every two seconds cron', '*/2 * * * * *', function () {\n  console.log(new Date);\n});\n```\n\n### Pass arguments\n\nPassing arguments can be done via wrapper function\n\n```js\nconst jobs = new JoSk({ /* ... */ });\nconst myVar = { key: 'value' };\nlet myLet = 'Some top level or env.variable (can get changed during runtime)';\n\nconst task = function (arg1, arg2, ready) {\n  //... code here\n  ready();\n};\n\njobs.setInterval((ready) =\u003e {\n  task(myVar, myLet, ready);\n}, 60 * 60000, 'taskA');\n\njobs.setInterval((ready) =\u003e {\n  task({ otherKey: 'Another Value' }, 'Some other string', ready);\n}, 60 * 60000, 'taskB');\n```\n\n### Async/Await with ready() callback\n\nFor long-running async tasks, or with callback-apis it might be needed to call `ready()` explicitly. Wrap task's body into `process.nextTick` to enjoy `await`/`async` combined with classic callback-apis\n\n```js\njobs.setInterval((ready) =\u003e {\n  process.nextTick(async () =\u003e {\n    try {\n      const result = await asyncCall();\n      waitForSomethingElse(async (error, data) =\u003e {\n        if (error) {\n          ready(); // \u003c-- Always run `ready()`, even if call was unsuccessful\n          return;\n        }\n\n        await saveCollectedData(result, [data]);\n        ready(); // \u003c-- End of the full execution\n      });\n    } catch (err) {\n      console.log(err)\n      ready(); // \u003c-- Always run `ready()`, even if call was unsuccessful\n    }\n  });\n}, 60 * 60000, 'longRunningTask1h'); // once every hour\n```\n\n### Clean up old tasks\n\nDuring development and tests you may want to clean up Adapter's Storage\n\n#### Clean up Redis\n\nTo clean up old tasks via Redis CLI use the next query pattern:\n\n```shell\nredis-cli --no-auth-warning KEYS \"josk:default:*\" | xargs redis-cli --raw --no-auth-warning DEL\n\n# If you're using multiple JoSk instances with prefix:\nredis-cli --no-auth-warning KEYS \"josk:prefix:*\" | xargs redis-cli --raw --no-auth-warning DEL\n```\n\n#### Clean up MongoDB\n\nTo clean up old tasks via MongoDB use the next query pattern:\n\n```js\n// Run directly in MongoDB console:\ndb.getCollection('__JobTasks__').remove({});\n// If you're using multiple JoSk instances with prefix:\ndb.getCollection('__JobTasks__PrefixHere').remove({});\n```\n\n### MongoDB connection fine tuning\n\n```js\n// Recommended MongoDB connection options\n// When used with ReplicaSet\nconst options = {\n  writeConcern: {\n    j: true,\n    w: 'majority',\n    wtimeout: 30000\n  },\n  readConcern: {\n    level: 'majority'\n  },\n  readPreference: 'primary'\n};\n\nMongoClient.connect('mongodb://url', options, (error, client) =\u003e {\n  // To avoid \"DB locks\" — it's a good idea to use separate DB from \"main\" application DB\n  const db = client.db('dbName');\n  const jobs = new JoSk({\n    adapter: new MongoAdapter({\n      db: db,\n    })\n  });\n});\n```\n\n## Notes\n\n- This package is perfect when you have multiple horizontally scaled servers for load-balancing, durability, an array of micro-services or any other solution with multiple running copies of code running repeating tasks that needs to run only once per application/cluster, not per server/instance;\n- Limitation — task must be run not often than once per two seconds (from 2 to ∞ seconds). Example tasks: [Email](https://www.npmjs.com/package/mail-time), SMS queue, Long-polling requests, Periodical application logic operations or Periodical data fetch, sync, and etc;\n- Accuracy — Delay of each task depends on storage and \"de-synchronization delay\". Trusted time-range of execution period is `task_delay ± (256 + Storage_Request_Delay)`. That means this package won't fit when you need to run a task with very precise delays. For other cases, if `±256 ms` delays are acceptable - this package is the great solution;\n- Use `opts.minRevolvingDelay` and `opts.maxRevolvingDelay` to set the range for *random* delays between executions. Revolving range acts as a safety control to make sure different servers __not__ picking the same task at the same time. Default values (`128` and `768`) are the best for 3-server setup (*the most common topology*). Tune these options to match needs of your project. Higher `opts.minRevolvingDelay` will reduce storage read/writes;\n- This package implements \"Read Locking\" via \"RedLock\" for Redis and dedicated `.lock` collection for MongoDB.\n\n## Running Tests\n\n1. Clone this package\n2. In Terminal (*Console*) go to directory where package is cloned\n3. Then run:\n\n```shell\n# Before running tests make sure NODE_ENV === development\n# Install NPM dependencies\nnpm install --save-dev\n\n# Before running tests you need\n# to have access to MongoDB and Redis servers\nREDIS_URL=\"redis://127.0.0.1:6379\" MONGO_URL=\"mongodb://127.0.0.1:27017/npm-josk-test-001\" npm test\n\n# If previous run has errors — add \"debug\" to output extra logs\nDEBUG=true REDIS_URL=\"redis://127.0.0.1:6379\" MONGO_URL=\"mongodb://127.0.0.1:27017/npm-josk-test-001\" npm test\n\n# Be patient, tests are taking around 6 mins\n```\n\n### Run Redis tests only\n\nRun Redis-related tests only\n\n```shell\n# Before running Redis tests you need to have Redis server installed and running\nREDIS_URL=\"redis://127.0.0.1:6379\" npm run test-redis\n\n# Be patient, tests are taking around 3 mins\n```\n\n### Run MongoDB tests only\n\nRun MongoDB-related tests only\n\n```shell\n# Before running Mongo tests you need to have MongoDB server installed and running\nMONGO_URL=\"mongodb://127.0.0.1:27017/npm-josk-test-001\" npm run test-mongo\n\n# Be patient, tests are taking around 3 mins\n```\n\n## Why JoSk?\n\n`JoSk` is *Job-Task* - Is randomly generated name by [\"uniq\" project](https://uniq.site)\n\n## Support our open source contribution:\n\n- Upload and share files using [☄️ meteor-files.com](https://meteor-files.com/?ref=github-josk-repo-footer) — Continue interrupted file uploads without losing any progress. There is nothing that will stop Meteor from delivering your file to the desired destination\n- Use [▲ ostr.io](https://ostr.io?ref=github-josk-repo-footer) for [Server Monitoring](https://snmp-monitoring.com), [Web Analytics](https://ostr.io/info/web-analytics?ref=github-josk-repo-footer), [WebSec](https://domain-protection.info), [Web-CRON](https://web-cron.info) and [SEO Pre-rendering](https://prerendering.com) of a website\n- Star on [GitHub](https://github.com/veliovgroup/josk)\n- Star on [NPM](https://www.npmjs.com/package/josk)\n- Star on [Atmosphere](https://atmospherejs.com/ostrio/cron-jobs)\n- [Sponsor via GitHub](https://github.com/sponsors/dr-dimitru)\n- [Support via PayPal](https://paypal.me/veliovgroup)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fveliovgroup%2Fjosk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fveliovgroup%2Fjosk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fveliovgroup%2Fjosk/lists"}