{"id":13659439,"url":"https://github.com/Riim/cellx","last_synced_at":"2025-04-24T14:33:17.931Z","repository":{"id":41516212,"uuid":"37900915","full_name":"Riim/cellx","owner":"Riim","description":"The ultra-fast implementation of reactivity for javascript","archived":false,"fork":false,"pushed_at":"2024-08-08T08:52:09.000Z","size":2196,"stargazers_count":460,"open_issues_count":8,"forks_count":15,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-11-02T00:02:58.208Z","etag":null,"topics":["typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/Riim.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":"2015-06-23T06:17:05.000Z","updated_at":"2024-10-23T22:06:47.000Z","dependencies_parsed_at":"2024-11-10T13:34:48.871Z","dependency_job_id":null,"html_url":"https://github.com/Riim/cellx","commit_stats":{"total_commits":346,"total_committers":5,"mean_commits":69.2,"dds":"0.011560693641618491","last_synced_commit":"3d7fc63324ef4a34e5d6507ae177559798fd551f"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riim%2Fcellx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riim%2Fcellx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riim%2Fcellx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riim%2Fcellx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Riim","download_url":"https://codeload.github.com/Riim/cellx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250643727,"owners_count":21464226,"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":["typescript"],"created_at":"2024-08-02T05:01:08.703Z","updated_at":"2025-04-24T14:33:17.596Z","avatar_url":"https://github.com/Riim.png","language":"TypeScript","funding_links":[],"categories":["JavaScript","TypeScript"],"sub_categories":[],"readme":"\u003cp\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/Riim/cellx/master/docs/images/logo.png\" width=\"237\" height=\"129\"\u003e\n\u003c/p\u003e\n\nUltra-fast implementation of reactivity for javascript/typescript.\n\n[![NPM version](https://badge.fury.io/js/cellx.svg)](https://www.npmjs.com/package/cellx)\n[![Build Status](https://travis-ci.org/Riim/cellx.svg?branch=master)](https://travis-ci.org/Riim/cellx)\n[![Coverage Status](https://coveralls.io/repos/github/Riim/cellx/badge.svg?branch=master)](https://coveralls.io/github/Riim/cellx?branch=master)\n\n## Installation\n\nThe following command installs cellx as a npm package:\n```\nnpm i -S cellx\n```\n\n## Usage example\n\n```js\nlet firstName = cellx('Матроскин');\nlet lastName = cellx('Кот');\n\nlet fullName = cellx(() =\u003e firstName.value + ' ' + lastName.value)\n\nfullName.subscribe(() =\u003e {\n    console.log('fullName:', fullName.value);\n});\n\nconsole.log(fullName.value);\n// =\u003e 'Матроскин Кот'\n\nfirstName.value = 'Шарик';\nlastName.value = 'Пёс';\n// =\u003e 'fullName: Шарик Пёс'\n```\n\nDespite the fact that the two dependencies of the cell `fullName` has been changed, change handler worked only once.\nImportant feature of cellx is that it tries to get rid of unnecessary calls of the event handlers as well as of\nunnecessary calculations of the dependent cells.\nIn combination with some special optimizations, this leads to an ideal speed of calculation of the complex dependencies\nnetworks.\nYou can find out more about this in the article [Big State Managers Benchmark](https://habr.com/ru/articles/707600/).\nYou may also be interested in the article\n[Разбираемся в сортах реактивности](https://habr.com/ru/companies/timeweb/articles/586450/).\n\n## Benchmark\n\nOne test, which is used for measuring the performance, generates grid with multiple \"layers\" each of which is composed\nof 4 cells. Cells of each next layer are calculated from the previous layer cells (except the first layer, which\ncontains initial values) by the formula A2=B1, B2=A1-C1, C2=B1+D1, D2=C1. After that start time is stored, values of all\nfirst layer cells are changed and time needed to update all last layer cells is measured.\nTest results (in milliseconds) for different number of layers:\n\n| Library ↓ \\ Number of computed layers →                 | 10      | 20                                | 30                                  | 50      | 100     | 1000    | 5000                                         |\n|---------------------------------------------------------|---------|-----------------------------------|-------------------------------------|---------|---------|---------|----------------------------------------------|\n| cellx                                                   |     \u003c~1 |                               \u003c~1 |                                 \u003c~1 |     \u003c~1 |     \u003c~1 |       4 |                                           20 |\n| VanillaJS (naive)                                       |     \u003c~1 |                                15 |                                1750 | \u003e300000 | \u003e300000 | \u003e300000 |                                      \u003e300000 |\n| [Knockout](http://knockoutjs.com/)                      |      10 | 750, increases in subsequent runs | 67250, increases in subsequent runs | \u003e300000 | \u003e300000 | \u003e300000 |                                      \u003e300000 |\n| [$jin.atom](https://github.com/nin-jin/pms-jin/)        |       2 |                                 3 |                                   3 |       4 |       6 |      40 |                                          230 |\n| [$mol_atom](https://github.com/nin-jin/mol)             |     \u003c~1 |                               \u003c~1 |                                 \u003c~1 |       1 |       2 |      20 | RangeError: Maximum call stack size exceeded |\n| [Kefir.js](https://rpominov.github.io/kefir/)           |      25 |                              2500 |                             \u003e300000 | \u003e300000 | \u003e300000 | \u003e300000 |                                      \u003e300000 |\n| [MobX](https://mobxjs.github.io/mobx/)                  |     \u003c~1 |                               \u003c~1 |                                 \u003c~1 |       2 |       3 |      40 | RangeError: Maximum call stack size exceeded |\n\nTest sources can be found in the folder [perf](https://github.com/Riim/cellx/tree/master/perf).\nDensity of connections in real applications is usually lower than in the present test, that is, if a certain delay in\nthe test is visible in 100 calculated cells (25 layers), in a real application, this delay will either be visible in the greater number of cells, or cells formulas will include some complex calculations (e.g., computation of one array from\nother).\n\n## Usage\n\nFunctional style:\n\n```js\nlet num = cellx(1);\nlet plusOne = cellx(() =\u003e num.value + 1);\n\nconsole.log(plusOne.value);\n// =\u003e 2\n```\n\nOOP style:\n\n```js\nimport { cellx, define } from 'cellx';\n\nclass User {\n\tname: string;\n\tnameInitial: string;\n\n\tconstructor(name: string) {\n\t\tdefine(this, {\n\t\t\tname,\n\t\t\tnameInitial: cellx(() =\u003e this.name.charAt(0).toUpperCase())\n\t\t});\n\t}\n}\n\nlet user = new User('Матроскин');\n\nconsole.log(user.nameInitial);\n// =\u003e 'M'\n```\n\nOOP style with decorators:\n\n```js\nimport { Computed, Observable } from 'cellx-decorators';\n\nclass User {\n\t@Observable name: string;\n\n\t@Computed get nameInitial() {\n\t\treturn this.name.charAt(0).toUpperCase();\n\t}\n\n\tconstructor(name: string) {\n\t\tthis.name = name;\n\t}\n}\n\nlet user = new User('Матроскин');\n\nconsole.log(user.nameInitial);\n// =\u003e 'M'\n```\n\n### Options\n\n#### put\n\nIt can be used for value processing on write and write redirection:\n\n```js\nfunction User() {\n    this.firstName = cellx('');\n    this.lastName = cellx('');\n\n    this.fullName = cellx(\n\t\t() =\u003e (this.firstName.value + ' ' + this.lastName.value).trim(),\n\t\t{\n\t\t\tput: (_cell, name) =\u003e {\n\t\t\t\tname = name.split(' ');\n\n\t\t\t\tthis.firstName.value = name[0];\n\t\t\t\tthis.lastName.value = name[1];\n\t\t\t}\n\t\t}\n\t);\n}\n\nlet user = new User();\n\nuser.fullName.value = 'Матроскин Кот';\n\nconsole.log(user.firstName.value);\n// =\u003e 'Матроскин'\nconsole.log(user.lastName.value);\n// =\u003e 'Кот'\n```\n\n#### validate\n\nValidates the value during recording and calculating.\n\nValidation during recording into the cell:\n\n```js\nlet num = cellx(5, {\n    validate: (value) =\u003e {\n        if (typeof value != 'number') {\n            throw TypeError('Must be a number');\n        }\n    }\n});\n\ntry {\n    num('I am string');\n} catch (err) {\n    console.log(err.message);\n    // =\u003e 'Must be a number'\n}\n\nconsole.log(num.value);\n// =\u003e 5\n```\n\nValidation during the calculation of the cell:\n\n```js\nlet someValue = cellx(5);\n\nlet num = cellx(() =\u003e someValue.value, {\n    validate: (value) =\u003e {\n        if (typeof value != 'number') {\n            throw TypeError('Must be a number');\n        }\n    }\n});\n\nnum.subscribe((err) =\u003e {\n    console.log(err.message);\n});\n\nsomeValue.value = 'I am string';\n// =\u003e 'Must be a number'\n\nconsole.log(value.value);\n// =\u003e 'I am string'\n```\n\n### Methods\n\n#### onChange\n\nAdds a change listener:\n\n```js\nlet num = cellx(5);\n\nnum.onChange((evt) =\u003e {\n    console.log(evt);\n});\n\nnum.value = 10;\n// =\u003e { prevValue: 5, value: 10 }\n```\n\n#### offChange\n\nRemoves previously added change listener.\n\n#### onError\n\nAdds a error listener:\n\n```js\nlet someValue = cellx(1);\n\nlet num = cellx(() =\u003e someValue.value, {\n    validate: (v) =\u003e {\n        if (v \u003e 1) {\n            throw RangeError('Oops!');\n        }\n    }\n});\n\nnum.onError((evt) =\u003e {\n    console.log(evt.error.message);\n});\n\nsomeValue.value = 2;\n// =\u003e 'Oops!'\n```\n\n#### offError\n\nRemoves previously added error listener.\n\n#### subscribe\n\nSubscribes to the events `change` and `error`. First argument comes into handler is an error object, second — an event.\n\n```js\nfullName.subscribe((err, evt) =\u003e {\n    if (err) {\n        // error handling\n    } else {\n        // other\n    }\n});\n```\n\n#### unsubscribe\n\nUnsubscribes from events `change` and `error`.\n\n## Dynamic actualisation of dependencies\n\nCalculated cell formula can be written so that a set of dependencies may change over time. For example:\n\n```js\nlet user = {\n    firstName: cellx(''),\n    lastName: cellx(''),\n\n    name: cellx(() =\u003e user.firstName.value || user.lastName.value)\n};\n```\n\nThere, while `firstName` is empty string, cell `name` uses `firstName` and `lastName` for calculate self value\nand change in any of them will lead to calculating `name`. If you assign to the `firstName` some not empty\nstring, then during recalculation value of cell `name` it will not come to reading `lastName` in the formula,\ni.e. the value of the cell `name` from this moment will not depend on `lastName`.\nIn such cases, cells automatically unsubscribe from dependencies insignificant for them and are not recalculated\nwhen they change. In the future, if the `firstName` again become an empty string, the cell `name` will re-subscribe\nto the `lastName`.\n\n## Synchronization of value with synchronous storage\n\n```js\nlet foo = cellx(() =\u003e localStorage.foo || 'foo', {\n\tput: ({ push }, value) =\u003e {\n\t\tlocalStorage.foo = value;\n\n\t\tpush(value);\n\t}\n});\n\nlet foobar = cellx(() =\u003e foo.value + 'bar');\n\nconsole.log(foobar.value); // =\u003e 'foobar'\nconsole.log(localStorage.foo); // =\u003e undefined\nfoo.value = 'FOO';\nconsole.log(foobar.value); // =\u003e 'FOObar'\nconsole.log(localStorage.foo); // =\u003e 'FOO'\n```\n\n## Synchronization of value with asynchronous storage\n\n```js\nlet request = (() =\u003e {\n\tlet value = 1;\n\n\treturn {\n\t\tget: (url) =\u003e new Promise((resolve, reject) =\u003e {\n            setTimeout(() =\u003e {\n                resolve({\n                    ok: true,\n                    value\n                });\n            }, 1000);\n        }),\n\n\t\tput: (url, params) =\u003e new Promise((resolve, reject) =\u003e {\n            setTimeout(() =\u003e {\n                value = params.value;\n\n                resolve({ ok: true });\n            }, 1000);\n        })\n\t};\n})();\n\nlet foo = cellx(({ push, fail }, next = 0) =\u003e {\n\trequest.get('http://...').then((res) =\u003e {\n\t\tif (res.ok) {\n\t\t\tpush(res.value);\n\t\t} else {\n\t\t\tfail(res.error);\n\t\t}\n\t});\n\n\treturn next;\n}, {\n\tput: ({ push, fail }, next) =\u003e {\n\t\trequest.put('http://...', { value: next }).then((res) =\u003e {\n\t\t\tif (res.ok) {\n\t\t\t\tpush(next);\n\t\t\t} else {\n\t\t\t\tfail(res.error);\n\t\t\t}\n\t\t});\n\t}\n});\n\nfoo.subscribe(() =\u003e {\n\tconsole.log('New foo value: ' + foo.value);\n\n\tfoo.value = 5;\n});\n\nconsole.log(foo.value);\n// =\u003e 0\n\n// =\u003e 'New foo value: 1'\n// =\u003e 'New foo value: 5'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRiim%2Fcellx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRiim%2Fcellx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRiim%2Fcellx/lists"}