{"id":28302031,"url":"https://github.com/bootstarted/krok","last_synced_at":"2026-04-25T23:35:27.698Z","repository":{"id":70938447,"uuid":"60601766","full_name":"bootstarted/krok","owner":"bootstarted","description":"Task management powered by redux.","archived":false,"fork":false,"pushed_at":"2016-10-15T00:29:09.000Z","size":40,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-10-19T16:48:53.867Z","etag":null,"topics":["bootstart","metalab","redux","task-runner","testing"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bootstarted.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"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}},"created_at":"2016-06-07T09:46:08.000Z","updated_at":"2020-05-04T09:08:15.000Z","dependencies_parsed_at":"2023-03-12T13:30:18.985Z","dependency_job_id":null,"html_url":"https://github.com/bootstarted/krok","commit_stats":{"total_commits":25,"total_committers":1,"mean_commits":25.0,"dds":0.0,"last_synced_commit":"e563e6eece5c4b0c2bf19066daafd4f4c7dbea5b"},"previous_names":["metalabdesign/krok"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bootstarted/krok","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bootstarted%2Fkrok","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bootstarted%2Fkrok/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bootstarted%2Fkrok/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bootstarted%2Fkrok/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bootstarted","download_url":"https://codeload.github.com/bootstarted/krok/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bootstarted%2Fkrok/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32280979,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["bootstart","metalab","redux","task-runner","testing"],"created_at":"2025-05-23T20:13:33.824Z","updated_at":"2026-04-25T23:35:27.692Z","avatar_url":"https://github.com/bootstarted.png","language":"JavaScript","readme":"# krok\n\nComplete task and resource management powered by redux.\n\n![build status](http://img.shields.io/travis/metalabdesign/krok/master.svg?style=flat)\n![coverage](http://img.shields.io/coveralls/metalabdesign/krok/master.svg?style=flat)\n![license](http://img.shields.io/npm/l/krok.svg?style=flat)\n![version](http://img.shields.io/npm/v/krok.svg?style=flat)\n![downloads](http://img.shields.io/npm/dm/krok.svg?style=flat)\n\n## Overview\n\n| Feature | krok | [undertaker] |\n| ------- | ------- | ------------ |\n| Async primitive   | Promise | [async-done]   |\n| Resource management | Yes | No |\n| Result forwarding | Yes | No |\n| Task registry | External | Internal |\n| State management | [redux] | Internal |\n| Concurrency | Controlled | Unlimited |\n| Deadlocks | Detected | Undetected |\n| Timeouts | Yes | No |\n| Retry | Yes | No |\n\nWhile there are [plenty](http://jakejs.com/) [of](http://gulpjs.com/) [task-runners](http://www.slant.co/topics/1276) most have an API designed around a very specific paradigm (streams, trees, etc.) and can't handle dependencies with resource-based results (that is to say results which require cleanup after they're used). `krok` exists to make it easy to inspect and control how large collections of interconnected tasks are run. It has no CLI, and it has no opinion about how your tasks should be run or stored.\n\nSome quick nomenclature to keep things consistent:\n\n**Task**: Representation of work to be done. Every task in `krok` has a unique string identifier. How that task is run is up to you.\n\n**Registry**: A collection of functions defining the behaviour for a domain of tasks. Includes things like how to run a task and what the dependencies of a given task are.\n\n**Resource**: A stateful result from a task which requires disposing when its no longer needed.\n\n**Dependencies**: Given a particular task, a list of tasks that must successfully complete before the original task can be run.\n\n## Usage\n\nInstall `krok` and its dependencies:\n\n```sh\nnpm install --save krok redux redux-thunk\n```\n\n### Simple\n\nIf you have a fixed set of tasks to run you can simply encode all of them directly.\n\n```javascript\nimport {runTask, reducer, createTaskRegistry} from 'krok';\nimport thunk from 'redux-thunk';\nimport {createStore, applyMiddleware} from 'redux';\n\nconst store = createStore(reducer, applyMiddleware(thunk));\n\nconst registry = createTaskRegistry({\n  // Called whenever a task has to be run. Return a promise representing the\n  // result of running the task.\n  run: (id, dependencies) =\u003e {\n    switch (id) {\n    case 'a':\n      // Perform computation for task `a`.\n      return Promise.resolve(3);\n    case 'b':\n      // Perform computation for task `b`.\n      return Promise.resolve(5 + dependencies[0]);\n    default:\n      // Trying to run any other task is failure.\n      return Promise.reject('No such task.');\n    }\n  },\n  // Called whenever dependencies need to be computed for a task. Return an\n  // array of other tasks that need to be run.\n  dependencies: (id) =\u003e {\n    switch (id) {\n    case 'a':\n      return [];\n    case 'b':\n      return ['a'];\n    default:\n      throw new TypeError();\n    }\n  },\n});\n\n// Run the task.\nconst result = store.dispatch(runTask(registry, 'b'));\n\n// Do something with the result.\nresult.then((b) =\u003e console.log('Task result:', b));\n```\n\n### Metadata\n\n```javascript\n\n```\n\n### Additional State\n\nSometimes you will want to do things with state.\n\n```javascript\nimport {runTask, reducer as taskReducer, createTaskRegistry} from 'krok';\nimport thunk from 'redux-thunk';\nimport {createStore, combineReducers, applyMiddleware} from 'redux';\n\n// Create your application's reducer.\nconst appReducer = handleActions({\n\n});\n\n// Create the combined reducer.\nconst reducer = combineReducers({\n  tasks: taskReducer,\n  app: appReducer,\n});\n\n// Create a selector to pick out the krok state.\nconst selector = (state) =\u003e state.tasks;\n\nconst store = createStore(reducer, applyMiddleware(thunk));\n\nconst registry = createTaskRegistry({\n  selector,\n  run: (id, dependencies) =\u003e {\n    switch (id) {\n    case 'a':\n      return Promise.resolve(3);\n    case 'b':\n      return Promise.resolve(5 + dependencies[0]);\n    default:\n      return Promise.reject('No such task.');\n    }\n  },\n  dependencies: (id) =\u003e {\n    switch (id) {\n    case 'a':\n      return [];\n    case 'b':\n      return ['a'];\n    default:\n      throw new TypeError();\n    }\n  },\n});\n\n\nconst result = store.dispatch(runTask(registry, 'b'));\nresult.then((b) =\u003e console.log('Task result:', b));\n```\n\n### Resource Management\n\nSometimes the result of running a task is just a simple value, like a plain JavaScript object. However, other times it can be something that has it's own state - database connections, web servers or file handles. After this kind of task result has been used, it needs to be cleaned up. You can provide a `dispose` handler in your registry for this purpose.\n\nInternally, `krok` handles all the necessary reference counting ensuring that both: as long as the result of a task is needed, its resource will be kept alive; and when the result is no longer needed, its resource will be disposed.\n\n```javascript\nconst registry = createTaskRegistry({\n  dispose: (id, result) =\u003e {\n    switch(id) {\n    case 'a':\n      return result.close();\n    default:\n      return Promise.resolve();\n    }\n  },\n  run: (id, dependencies) =\u003e {\n    switch (id) {\n    case 'a':\n      return db.connect();\n    case 'b':\n      return dependencies[0].query('some db query');\n    default:\n      return Promise.reject('No such task.');\n    }\n  },\n  dependencies: (id) =\u003e {\n    switch (id) {\n    case 'a':\n      return [];\n    case 'b':\n      return ['a'];\n    default:\n      throw new TypeError();\n    }\n  },\n});\n```\n\nNote that when _you_ run a task, `krok` does not know how long you expect to use the resource for and _DOES NOT_ automatically handle the reference count for that task. If you're thinking of using a resource, consider first if it's possible to make a task that consumes that resource into a concrete result so you do not have to manually manage references yourself.\n\nHowever, if you do need access to a long-running resource, `krok` provides you with the mechanism for updating the reference count yourself.\n\n```javascript\n// Mark that you want to keep the handle for `a` available.\nstore.dispatch(refTask('a'))\n// Run the task.\nstore.dispatch(runTask('a')).then((result) =\u003e {\n  // Do something with the result for as long as you need to.\n  // When you're done unref the task. Invoking this will cause the reference\n  // count to drop to 0 and the task will be disposed.\n  store.dispatch(unrefTask('a'));\n});\n```\n\n### Orthogonal Tasks\n\nA particular task (and all its transitive dependencies) have to be handled by the same registry. That is to say if `a` depends on `b`, and `b` depends on `c`, then all of `a`, `b` and `c` must be handled by the same registry. However, if you have tasks that are not connected like this you can have each group handled by different registries. Note that the task identifier is independent of the registry and you are still responsible for ensuring uniqueness.\n\n```javascript\n// Create registries.\nconst registry1 = createTaskRegistry(...);\nconst registry2 = createTaskRegistry(...);\n\n// `registry1` will handle how to manage tasks `a`, `b` and `c`.\nconst resultA = store.dispatch(runTask(registry1, 'a'));\n// `register2` will handle how to manage task `d`.\nconst resultD = store.dispatch(runTask(registry2, 'd'));\n```\n\n### Concurrency Control\n\nSometimes you may wish to limit how many tasks can run in parallel.\n\nAn extremely simple mechanism:\n\n```javascript\nconst registry = createTaskRegistry({\n  // TODO: Finish this.\n  schedule: (next, active) =\u003e {\n    return next.slice(0, Math.max(0, 3 - active.length));\n  },\n});\n```\n\nYou can do more complex scheduling using buckets:\n\n```javascript\nconst registry = createTaskRegistry({\n  // TODO: Finish this.\n  schedule: (next) =\u003e {\n    return next.slice(0, 3);\n  },\n});\n```\n\n### Task Priority\n\nSometimes you may wish to schedule some tasks sooner than others.\n\n```javascript\nimport flow from 'lodash/fp/flow';\nimport sortBy from 'lodash/fp/sortBy';\nimport take from 'lodash/fp/take';\n\nconst xxx = flow(\n  sortBy('priority'),\n  take(5)\n);\n\nconst registry = createTaskRegistry({\n  // TODO: Finish this.\n  schedule: (next) =\u003e {\n    return xxx(next);\n  },\n});\n```\n\n### Resource Limiting\n\nSometimes you may wish to control how many resources of one type remain active – e.g. only allow 3 concurrent database sessions. You can use a similar mechanism to concurrency control to achieve this.\n\n```javascript\nconst registry = createTaskRegistry({\n  // TODO: Finish this. Use `refs` to count active resources.\n  schedule: (next) =\u003e {\n    countBy('type', refs);\n  }\n});\n```\n\n### Deadlocks\n\nIf you're not careful when customizing the scheduler or you create circular dependencies, you can create deadlocks. Generally, `krok` is capable of detecting them and will automatically fail any task caught inside a deadlock.\n\nThis is a trivial dependency deadlock that `krok` will detect:\n\n```javascript\nconst registry = createTaskRegistry({\n  run: (id, dependencies) =\u003e {\n    switch (id) {\n    case 'a':\n      return Promise.resolve(dependencies[0] + 1);\n    default:\n      return Promise.reject('No such task.');\n    }\n  },\n  dependencies: (id) =\u003e {\n    switch (id) {\n    case 'a':\n      return ['a'];\n    default:\n      throw new TypeError();\n    }\n  },\n});\n```\n\nThis is a trivial scheduler deadlock that `krok` will detect:\n\n```javascript\nconst registry = createTaskRegistry({\n  schedule: () =\u003e []\n});\n```\n\n### Debugging\n\nBecause `krok` uses [redux] under the hood, you can use all the tooling available to the [redux] ecosystem to inspect the state of the system as it runs. For example, you can use [redux-logger] or [redux-cli-logger] to track everything that happens.\n\n```javascript\nimport createLogger from 'redux-logger';\n\nconst logger = createLogger(...);\nconst store = createStore(reducer, applyMiddleware(thunk, logger));\n```\n\n### Reporting\n\nReporting is a little bit trickier with `krok`. The only official mechanism to detect changes is by using [redux's store subscription].\n\n```javascript\nlet state = null;\nlet oldState = store.getState();\nstore.subscribe(() =\u003e {\n  state = store.getState();\n  // Report on whatever you want here.\n  oldState = state;\n});\n```\n\nAlternatively, if you're building a reporter that makes use of [react] (either with [react-dom] for the web or [react-blessed] for console), you can use [react-redux] to handle all the necessary state observations.\n\n## Configuration\n\nThe options (and defaults) to `createTaskRegistry` are described below:\n\n```javascript\n{\n  /**\n   * Clean up the result produced by a task. This is called whenever the\n   * resource created by your task is no longer needed.\n   * @param {Any} task Task whose resource needs disposing.\n   * @param {Any} value The resource created by the task.\n   * @returns {Promise} The result of disposing the resource.\n   */\n  dispose = (task, value) =\u003e Promise.resolve(),\n\n  /**\n   * Fetch the part of the global redux state atom that has the `krok` state.\n   * You can use this to combine `krok` with other redux reducers.\n   * @param {Object} state Global redux state atom.\n   * @returns {Object} The `krok` state atom.\n   */\n  selector = (state) =\u003e state,\n\n  /**\n   * Fetch a task. Internally `krok` only uses `id` to track tasks, but you\n   * may wish to attach additional data to a particular task. Whatever you\n   * return here will be passed as the `task` argument to the other registry\n   * functions.\n   * @param {String} id Task identifier.\n   * @returns {Any} Task representation for given identifier.\n   */\n  task = (id) =\u003e id,\n\n  /**\n   * Fetch the list of dependencies for a given task.\n   * @param {Any} task The current task whose dependencies are needed.\n   * @returns {Array} List of dependencies.\n   */\n  dependencies = (task) =\u003e [],\n\n  /**\n   * Execute a given task.\n   * @param {Any} task The current task.\n   * @returns {Promise} The result of executing the task.\n   */\n  run = (task) =\u003e Promise.resolve(),\n\n  /**\n   * Schedule the next unit of work to be run. The only tasks given are tasks\n   * that are actually able to be scheduled (i.e. all there dependencies have\n   * been met).\n   * Note that you can create deadlocks here when there are no tasks currently\n   * running, there are tasks pending _and_ you schedule no more work. All\n   * pending tasks will be failed in this case.\n   * @param {Array} tasks List of tasks that are available for running.\n   * @returns {Array} List of tasks to be run.\n   */\n  schedule = (tasks) =\u003e tasks,\n}\n```\n\n\n[undertaker]: https://github.com/gulpjs/undertaker\n[async-done]: https://github.com/gulpjs/async-done\n[redux]: https://github.com/reactjs/redux\n[redux-thunk]: https://github.com/gaearon/redux-thunk\n[store subscription]: http://redux.js.org/docs/api/Store.html#subscribe\n[react]: https://facebook.github.io/react/\n[react-dom]: https://www.npmjs.com/package/react-dom\n[react-blessed]: https://github.com/Yomguithereal/react-blessed\n[react-redux]: https://github.com/reactjs/react-redux\n[redux-cli-logger]: https://github.com/fasterthanlime/redux-cli-logger\n[redux-logger]: https://github.com/evgenyrodionov/redux-logger\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbootstarted%2Fkrok","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbootstarted%2Fkrok","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbootstarted%2Fkrok/lists"}