{"id":28220887,"url":"https://github.com/morulus/sequencex","last_synced_at":"2025-06-26T01:02:40.294Z","repository":{"id":66348138,"uuid":"135951772","full_name":"morulus/sequencex","owner":"morulus","description":"Communicating sequential asynchronous operations manager","archived":false,"fork":false,"pushed_at":"2018-09-02T22:25:34.000Z","size":65,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-10T09:41:16.596Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morulus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-06-04T00:32:43.000Z","updated_at":"2018-06-04T02:07:02.000Z","dependencies_parsed_at":"2023-02-24T00:30:54.316Z","dependency_job_id":null,"html_url":"https://github.com/morulus/sequencex","commit_stats":{"total_commits":6,"total_committers":1,"mean_commits":6.0,"dds":0.0,"last_synced_commit":"d6a597cb4ce438322850cdc83ad3e5d46fdb66d6"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/morulus/sequencex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morulus%2Fsequencex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morulus%2Fsequencex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morulus%2Fsequencex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morulus%2Fsequencex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morulus","download_url":"https://codeload.github.com/morulus/sequencex/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morulus%2Fsequencex/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260851348,"owners_count":23072559,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-18T04:16:25.468Z","updated_at":"2025-06-26T01:02:40.241Z","avatar_url":"https://github.com/morulus.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"SequenceX\n==\n\nSequenceX is a generator-based communicating sequential asynchronous operations manager. Inspired by the redux-saga, by ideology close to [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes). But **it is not strict implementation of CSP pattern**.\n\nDesigned as a universal tool for managing an asynchronous sequence flow. It does not impose restrictions on use in a particular engine or framework. All you need is a node grater than [6.4.0](https://nodejs.org/en/blog/release/v6.4.0/) or [regenerator]()  (also babel [tranform-regenerator](https://babeljs.io/docs/plugins/transform-regenerator/)) transpiler for older versions of node and compatibility with old browsers.\n\nBeta\n--\n\n**The current major version still lower than 1. It is not fully tested. Most likely, its API and functionality will be changed. Use it at your own risk.**\n\nHowever, it inherits the experience from the previous project [reciprocator](https://github.com/morulus/reciprocator) and therefore it is not quite raw.\n\nTo develop the functionality and debugging was spent more than a month and few years of cultivation the idea. But a final understanding of the principles has come only now. So I started from blank project.\n\nIf you'd like to contribute project, please contact me, contact me or make pull requests. But I do not guarantee that I will approve all concept changes.\n\nDocs\n--\n\n- [API reference](https://github.com/morulus/sequencex/blob/master/docs/api.md)\n\nPrinciple and inspiration\n--\n\nSequenceX is a wrapper for functions, generators, and promises, which allows you to execute them as a sequence.\n\nMain princliple of SequenceX is that **any returned executable value will be executed**. Thus, if your function returns another function, or promise, or generator, these will be resolved.\n\nIn general, the sequence resembles a chain of Promise, but it will also involve functions and generators.\n\nThis approach is already partially implemented in the [redux-saga](https://github.com/redux-saga/redux-saga). I tried to make it more independent, so that it could be used with anything (not only with redux).\n\n### Behavior of functions\n\nFor example, if your function returns another function, that another function will be invoked automatically in the same context. If that function returns a function again, it will be invoked too. And so on, until the final result is obtained.\n\n```js\nimport Sequence from 'sequencex'\n\nconst deepValue =\u003e () =\u003e () =\u003e () =\u003e 33;\n\nnew Sequence(deepValue)\n  .then(console.log)\n// 33\n\n```\n\n### Behavior of promises\n\nAny returned promise will be awaited for the final result.\n\n```js\nimport Sequence from 'sequencex'\n\nnew Sequence(new Promise(resolve =\u003e setTimeout(\n  () =\u003e resolve(33),\n  3300\n)))\n  .then(console.log)\n// 33\n\n```\n\n### Behavior of generators\n\nAny returned es6 generator (or a generator result) will be executed as sub-sequence until it returns final value.\n\n```js\nimport Sequence from 'sequencex'\n\nconst deferredCalc = function* (a, b) {\n  yield new Promise(resolve =\u003e setTimeout(resolve, 3330))\n  yield a + b;\n}\n\nnew Sequence(function* () {\n  const a = yield 11;\n  const b = yield 22;\n  return deferredCalc(a, b);\n})\n  .then(console.log)\n// 33\n```\n\nKeep in the mind, and there is an important part of the behavior of SequenceX, that **only last generator result will be returned as result to the upper handler**.\n\nAnd, if the generator contains no return operator, the value returned by last operator _yield_ will be considered as the final result.\n\n```js\nnew Sequence(function *() {\n  yield 1;\n  yield 2;\n  yield 3;\n})\n  .then(console.log)\n// 3\n```\n\n### Executable values and payload\n\nFunctions, promises, and generators are considered to be **executable values**. It means that if you return of one these type to the runner, it will be executed.\n\nBut all other types of values (as *numbers, strings, booleans, objects, symbols, null and undefined*) will be considered as, so-called, **payload** and will be returned to the parent function (or next generator step) as the result.\n\nThis means that you can not return a function as a final result of the sequence.\n\nHere's an example when I try to get property selector with Ramda. Ramda works by carrying pattern and if we pass not enough arguments it returns another function, which will be immediately called by SequenceX runner without arguments and this process will last indefinitely.\n\n```js\n// Anti-pattern\nimport R from 'ramda'\n\n// Will not work as expected\nnew Sequence(function () {\n  return R.pick(['id'])\n})\n  .then(console.log)\n// This script never stops its execution\n// because Ramda.pick will always return a function\n```\n\nIf you strongly need to return an executable value as the result there is a helper function called `fx.payload` which in a special way pushes the result upward without execution.\n\n```js\nimport Sequence, { fx } from 'sequencex'\nimport R from 'ramda'\n\n// Will not work as expected\nnew Sequence(function () {\n  return fx.payload(R.pick(['id']))\n})\n  .then(console.log)\n// function r(e){return 0===arguments.length||n(e)?r:t.apply(this,arguments)}\n```\n\nOr you can wrap your executable value into the array or an object to prevent its execution.\n\n```js\nnew Sequence(() =\u003e {\n  return [() =\u003e {}] // This function won't be executed, because\n                    // it wrapped into the array\n})\n  .then(console.log)\n// [function ()]\n```\n\nAsync result\n--\n\nSequenceX is always asynchronious and returns a Promise, even if the initial function synchronious.\n\n```js\nnew Sequence(function () {\n  return 33;\n}).then(console.log)\n// 33\n```\n\nCreating standalone sequences\n--\n\nSequenceX allows you to wrap code with SequenceX  runner, thus obtaining a standalone asynchronous function.\n\n```js\nimport Sequence from 'sequencex'\n\nexport default Sequence.create(function* (url) {\n  const serverResponse = yield fetch(url);\n  return serverResponse.json();\n});\n```\n\nWhich will allow you to execute sequences without any additional access to the SequenceX API. Also, such functions can take arguments.\n\n````js\nimport fetchUrl from './sequences/fetchUrl'\n\nfetchUrl('/entity/33')\n  .then(console.log)\n// { type: 'entity', id: 33 }\n````\n\nOr use from an another sequence.\n\n````js\nimport Sequence from 'sequencex'\nimport fetchUrl from './sequences/fetchUrl'\n\nnew Sequence(function* () {\n  const data = yield fetchUrl('/entity/33')\n  console.log(data); // { type: 'entity', id: 33 }\n});\n````\n\n\nContext\n---\n\nEvery subsequence you can access context of the initial function. This means that you can manage the state and use API of your application from the sequence.\n\n```js\nconst Sequence = require('sequencex')\n\nSequence.apply(process, function() {\n  return function () {\n    const cwd = this.cwd();\n  }\n});\n```\n\nEven if your nested sequence includes arrow-functions (which by nature can not access dynamic context), you can access context by returning normal functions.\n\nHere is example how to get redux store state from some nested arrow-function.\n\n```js\nimport { createStore } from 'redux'\nimport { apply } from 'sequencex'\n\n// Define helper, that can access state of the context\nfunction getState() {\n  return this.getState();\n}\n\n// Usual redux store\nconst store = createStore(state =\u003e state, {\n  sequence: 'X'\n});\n\n// Apply sequence with store as context\napply(\n  store,\n  function* () {\n    // Create few nested sequences\n    return () =\u003e {\n      // This function have no access to the store\n      return () =\u003e {\n        // This function have no access to the store too\n        // But you can return a function, which have it.\n        return getState;\n      }\n    }\n  }\n)\n  .then(console.log)\n// {sequence:'X'}\n```\n\nChannels\n--\n\nFoundation stone of CSP pattern is a channels. Channel can accumulate and radiate values. SequenceX channel is not an object, but function, that returns promise with next value each time it called.\n\nIf channel is empty, then returned promise will be unresolved, until next value will be pushed to channel.\n\nSequence channels are not a part of fx collection and can be used separately of sequence flow.\n\n```js\nconst {\n  createChannel\n} = require(\"../lib\");\n\nconst messages = createChannel();\n\nmessages.push(\"Hello\");\nmessages.push(\"Channel\");\n\nmessages().then(console.log); // Hello\nmessages().then(console.log); // World\n\n```\nHere is few examples of practicle usage of SequenceX channels.\n\nImagine situation when you should make request to the server each time user click on document. And you can not make more than 6 requests at a time.\n\nHere is approximate solution on pure javascript:\n\n```js\nconst requestsQueue = [];\nlet activeRequestsCount = 0;\n\nfunction sendRequest(event) {\n  if (activeRequests \u003e= 6) {\n    requestsQueue.push(event);\n  } else {\n    activeRequestsCount++;\n    fetch('api/click')\n      .then(() =\u003e {\n        activeRequestsCount--;\n        if (requestsQueue.length) {\n          requestsQueue\n            .slice(0, 6 - activeRequestsCount)\n            .map(sendRequest)\n        }\n      })\n  }\n}\n\ndocument.addEventListener('click', sendRequest);\n```\n\n_I'm not sure this example real works, but it shows how cumbersome it looks_\n\nAnd here how it can be solved with channels:\n\n```js\nimport { createChannel } from 'sequencex'\n\nnew Sequence(function *() {\n  // Create two channels\n  const clicks = createChannel();\n  const requests = createChannel();\n\n  // Add every click event to channel `clicks`\n  document.addEventListener('click', clicks.push)\n\n  // Handle each next event through a while loop\n  while (yield clicks) {\n    // Await superfluous requests\n    while (requests.count() \u003e= 6) {\n      yield requests;\n    }\n\n    // Create http request and push response\n    // to channel `responses`\n    requests.push(fetch('api/click'))\n  }\n})\n```\n\nAuthor\n--\n\nVladimir Kalmykov \u003cvladimirmorulus@gmail.com\u003e\n\nLicense\n--\n\nMIT, 2018\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorulus%2Fsequencex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorulus%2Fsequencex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorulus%2Fsequencex/lists"}