{"id":13605600,"url":"https://github.com/elbywan/hyperactiv","last_synced_at":"2025-05-16T18:07:03.390Z","repository":{"id":28029397,"uuid":"115869903","full_name":"elbywan/hyperactiv","owner":"elbywan","description":"A super tiny reactive library. :zap:","archived":false,"fork":false,"pushed_at":"2024-09-27T14:04:27.000Z","size":1817,"stargazers_count":441,"open_issues_count":6,"forks_count":22,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-13T17:47:58.584Z","etag":null,"topics":["computed","es6-proxies","javascript","properties","react","reactive"],"latest_commit_sha":null,"homepage":"","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/elbywan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":"elbywan","custom":["https://www.paypal.me/elbywan"]}},"created_at":"2017-12-31T15:13:10.000Z","updated_at":"2025-03-14T09:11:23.000Z","dependencies_parsed_at":"2023-01-14T07:58:51.655Z","dependency_job_id":"09f05639-b183-447a-9f03-b95e7a11d52b","html_url":"https://github.com/elbywan/hyperactiv","commit_stats":{"total_commits":247,"total_committers":7,"mean_commits":"35.285714285714285","dds":"0.22267206477732793","last_synced_commit":"9fdf71f39446bfcbe4a44189b5005eea4cee65bf"},"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fhyperactiv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fhyperactiv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fhyperactiv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fhyperactiv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elbywan","download_url":"https://codeload.github.com/elbywan/hyperactiv/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254582905,"owners_count":22095518,"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":["computed","es6-proxies","javascript","properties","react","reactive"],"created_at":"2024-08-01T19:01:00.611Z","updated_at":"2025-05-16T18:07:03.374Z","avatar_url":"https://github.com/elbywan.png","language":"JavaScript","funding_links":["https://github.com/sponsors/elbywan","https://www.paypal.me/elbywan"],"categories":["Uncategorized","JavaScript","State Managers"],"sub_categories":["Uncategorized","Signals"],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg alt=\"Hyperactiv logo\" src=\"https://cdn.rawgit.com/elbywan/hyperactiv/747e759b/logo.svg\" width=\"100px\"/\u003e\n\t\u003cbr\u003e\n    Hyperactiv\u003cbr\u003e\n    \u003ca href=\"https://www.npmjs.com/package/hyperactiv\"\u003e\u003cimg alt=\"npm-badge\" src=\"https://img.shields.io/npm/v/hyperactiv.svg?colorB=ff733e\" height=\"20\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/elbywan/hyperactiv/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"ci-badge\" src=\"https://github.com/elbywan/hyperactiv/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href='https://coveralls.io/github/elbywan/hyperactiv?branch=master'\u003e\u003cimg src='https://coveralls.io/repos/github/elbywan/hyperactiv/badge.svg?branch=master' alt='Coverage Status' /\u003e\u003c/a\u003e\n    \u003ca href=\"https://bundlephobia.com/result?p=hyperactiv\"\u003e\u003cimg src='https://img.shields.io/bundlephobia/minzip/hyperactiv.svg'/\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/elbywan/hyperactiv/blob/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"license-badge\" height=\"20\"\u003e\u003c/a\u003e\n\u003c/h1\u003e\n\n \u003ch4 align=\"center\"\u003e\n    A super tiny reactive library. ⚡️\u003cbr\u003e\n    \u003cbr\u003e\n\u003c/h4\u003e\n\n## Description\n\nHyperactiv is a super small (~ 1kb minzipped) library which **observes** object mutations and **computes** functions depending on those changes.\n\nIn other terms whenever a property from an observed object is **mutated**, every function that **depend** on this property are **called** right away.\n\nOf course, Hyperactiv **automatically** handles these dependencies so you **never** have to explicitly declare anything. ✨\n\n----\n\n#### Minimal working example\n\n```js\nimport hyperactiv from 'hyperactiv'\nconst { observe, computed } = hyperactiv\n\n// This object is observed.\nconst observed = observe({\n    a: 1,\n    b: 2,\n    c: 0\n})\n\n// Calling computed(...) runs the function and memorize its dependencies.\n// Here, the function depends on properties 'a' and 'b'.\ncomputed(() =\u003e {\n    const { a, b } = observed\n    console.log(`a + b = ${a + b}`)\n})\n// Prints: a + b = 3\n\n// Whenever properties 'a' or 'b' are mutated…\nobserved.a = 2\n// The function will automagically be called.\n// Prints: a + b = 4\n\nobserved.b = 3\n// Prints: a + b = 5\n\nobserved.c = 1\n// Nothing depends on 'c', so nothing will happen.\n```\n\n## Demo\n\n**[Paint demo](https://elbywan.github.io/hyperactiv/paint)**\n\n**[React store demo](https://elbywan.github.io/hyperactiv/todos)**\n\n**[React hooks demo](https://github.com/elbywan/hyperactiv-hooks-demo)**\n\n## Setup\n\n```bash\nnpm i hyperactiv\n```\n\n```html\n\u003cscript src=\"https://unpkg.com/hyperactiv\"\u003e\u003c/script\u003e\n```\n\n## Import\n\n**Hyperactiv is bundled as an UMD package.**\n\n```js\n// ESModules\nimport hyperactiv from 'hyperactiv'\n```\n\n```js\n// Commonjs\nconst hyperactiv = require('hyperactiv')\n```\n\n```js\n// Global variable\nconst { computed, observe, dispose } = hyperactiv\n```\n\n## Usage\n\n#### 1. Observe object and arrays\n\n```js\nconst object = observe({ one: 1, two: 2 })\nconst array = observe([ 3, 4, 5 ])\n```\n\n#### 2. Define computed functions\n\n```js\nlet sum = 0\n\n// This function calculates the sum of all elements,\n// which is 1 + 2 + 3 + 4 + 5 = 15 at this point.\nconst calculateSum = computed(() =\u003e {\n    sum = [\n        ...Object.values(object),\n        ...array\n    ].reduce((acc, curr) =\u003e acc + curr)\n})\n\n// A computed function is called when declared.\nconsole.log(sum) // -\u003e 15\n```\n\n#### 3. Mutate observed properties\n\n```js\n// calculateSum will be called each time one of its dependencies has changed.\n\nobject.one = 2\nconsole.log(sum) // -\u003e 16\narray[0]++\nconsole.log(sum) // -\u003e 17\n\narray.unshift(1)\nconsole.log(sum) // -\u003e 18\narray.shift()\nconsole.log(sum) // -\u003e 17\n```\n\n#### 4. Release computed functions\n\n```js\n// Observed objects store computed function references in a Set,\n// which prevents garbage collection as long as the object lives.\n// Calling dispose allows the function to be garbage collected.\ndispose(calculateSum)\n```\n\n## Add-ons\n\n#### Additional features that you can import from a sub path.\n\n- **[hyperactiv/react](https://github.com/elbywan/hyperactiv/tree/master/src/react)**\n\n*A simple but clever react store.*\n\n- **[hyperactiv/http](https://github.com/elbywan/hyperactiv/tree/master/src/http)**\n\n*A reactive http cache.*\n\n- **[hyperactiv/handlers](https://github.com/elbywan/hyperactiv/tree/master/src/handlers)**\n\n*Utility callbacks triggered when a property is mutated.*\n\n- **[hyperactiv/classes](https://github.com/elbywan/hyperactiv/tree/master/src/classes)**\n\n*An Observable class.*\n\n- **[hyperactiv/websocket](https://github.com/elbywan/hyperactiv/tree/master/src/websocket)**\n\n*Hyperactiv websocket implementation.*\n\n## Performance\n\nThis repository includes a [benchmark folder](https://github.com/elbywan/hyperactiv/tree/master/bench) which pits `hyperactiv` against other libraries.\n\n**Important: the benchmarked libraries are not equivalent in terms of features, flexibility and developer friendliness.**\n\nWhile not the best in terms of raw performance `hyperactiv` is still reasonably fast and I encourage you to have a look at the different implementations to compare the library APIs. [For instance there is no `.get()` and `.set()` wrappers when using `hyperactiv`](https://github.com/elbywan/hyperactiv/blob/master/bench/layers.mjs#L361).\n\n**Here are the raw results: _(100 runs per tiers, average time ignoring the 10 best \u0026 10 worst runs)_**\n\n![bench](./docs/bench.png)\n\n\u003e Each tier nests observable objects X (10/100/500/1000…) times and performs some computations on the deepest one. This causes reactions to propagate to the whole observable tree.\n\n\n_**Disclaimer**: I adapted the code from [`maverickjs`](https://github.com/maverick-js/observables/tree/main/bench) which was itself a rewrite of the benchmark from [`cellx`](https://github.com/Riim/cellx#benchmark). I also wrote some MobX code which might not be the best in terms of optimization since I am not very familiar with the API._\n\n## Code samples\n\n#### A simple sum and a counter\n\n```js\n// Observe an object and its properties.\nconst obj = observe({\n    a: 1,\n    b: 2,\n    sum: 0,\n    counter: 0\n})\n\n// The computed function auto-runs by default.\ncomputed(() =\u003e {\n    // This function depends on a, b and counter.\n    obj.sum = obj.a + obj.b\n    // It also sets the value of counter, which is circular (get \u0026 set).\n    obj.counter++\n})\n\n// The function gets executed when computed() is called…\nconsole.log(obj.sum)     // -\u003e 3\nconsole.log(obj.counter) // -\u003e 1\nobj.a = 2\n// …and when a or b are mutated.\nconsole.log(obj.sum)     // -\u003e 4\nconsole.log(obj.counter) // -\u003e 2\nobj.b = 3\nconsole.log(obj.sum)     // -\u003e 5\nconsole.log(obj.counter) // -\u003e 3\n```\n\n#### Nested functions\n\n```js\nconst obj = observe({\n    a: 1,\n    b: 2,\n    c: 3,\n    d: 4,\n    totalSum: 0\n})\n\nconst aPlusB = () =\u003e {\n    return obj.a + obj.b\n}\nconst cPlusD = () =\u003e {\n    return obj.c + obj.d\n}\n\n// Depends on a, b, c and d.\ncomputed(() =\u003e {\n    obj.totalSum = aPlusB() + cPlusD()\n})\n\nconsole.log(obj.totalSum) // -\u003e 10\nobj.a = 2\nconsole.log(obj.totalSum) // -\u003e 11\nobj.d = 5\nconsole.log(obj.totalSum) // -\u003e 12\n```\n\n#### Chaining computed properties\n\n```js\nconst obj = observe({\n    a: 0,\n    b: 0,\n    c: 0,\n    d: 0\n})\n\ncomputed(() =\u003e { obj.b = obj.a * 2 })\ncomputed(() =\u003e { obj.c = obj.b * 2 })\ncomputed(() =\u003e { obj.d = obj.c * 2 })\n\nobj.a = 10\nconsole.log(obj.d) // -\u003e 80\n```\n\n#### Asynchronous computations\n\n```js\n// Promisified setTimeout.\nconst delay = time =\u003e new Promise(resolve =\u003e setTimeout(resolve, time))\n\nconst obj = observe({ a: 0, b: 0, c: 0 })\nconst multiply = () =\u003e {\n    obj.c = obj.a * obj.b\n}\nconst delayedMultiply = computed(\n\n    // When dealing with asynchronous functions\n    // wrapping with computeAsync is essential to monitor dependencies.\n\n    ({ computeAsync }) =\u003e\n        delay(100).then(() =\u003e\n            computeAsync(multiply)),\n    { autoRun: false }\n)\n\ndelayedMultiply().then(() =\u003e {\n    console.log(obj.b) // -\u003e 0\n    obj.a = 2\n    obj.b = 2\n    console.log(obj.c) // -\u003e 0\n    return delay(200)\n}).then(() =\u003e {\n    console.log(obj.c) // -\u003e 4\n})\n```\n\n#### Batch computations\n\n```js\n// Promisified setTimeout.\nconst delay = time =\u003e new Promise(resolve =\u003e setTimeout(resolve, time))\n\n// Enable batch mode.\nconst array = observe([0, 0, 0], { batch: true })\n\nlet sum = 0\nlet triggerCount = 0\n\nconst doSum = computed(() =\u003e {\n    ++triggerCount\n    sum = array.reduce((acc, curr) =\u003e acc + curr)\n})\n\nconsole.log(sum) // -\u003e 0\n\n// Even if we are mutating 3 properties, doSum will only be called once asynchronously.\n\narray[0] = 1\narray[1] = 2\narray[2] = 3\n\nconsole.log(sum) // -\u003e 0\n\ndelay(10).then(() =\u003e {\n    console.log(`doSum triggered ${triggerCount} time(s).`) // -\u003e doSum triggered 2 time(s).\n    console.log(sum) // -\u003e 6\n})\n```\n\n#### Observe only some properties\n\n```js\nconst object = {\n    a: 0,\n    b: 0,\n    sum: 0\n}\n\n// Use props to observe only some properties\n// observeA reacts only when mutating 'a'.\n\nconst observeA = observe(object, { props:  ['a'] })\n\n// Use ignore to ignore some properties\n// observeB reacts only when mutating 'b'.\n\nconst observeB = observe(object, { ignore: ['a', 'sum'] })\n\nconst doSum = computed(function() {\n    observeA.sum = observeA.a + observeB.b\n})\n\n// Triggers doSum.\n\nobserveA.a = 2\nconsole.log(object.sum) // -\u003e 2\n\n// Does not trigger doSum.\n\nobserveA.b = 1\nobserveB.a = 1\nconsole.log(object.sum) // -\u003e 2\n\n// Triggers doSum.\n\nobserveB.b = 2\nconsole.log(object.sum) // -\u003e 3\n```\n\n#### Automatically bind methods\n\n```javascript\nlet obj = new SomeClass()\nobj = observe(obj, { bind: true })\nobj.someMethodThatMutatesObjUsingThis()\n// observe sees all!\n```\n\n#### This and class syntaxes\n\n```js\nclass MyClass {\n    constructor() {\n        this.a = 1\n        this.b = 2\n\n        const _this = observe(this)\n\n        // Bind computed functions to the observed instance.\n        this.doSum = computed(this.doSum.bind(_this))\n\n        // Return an observed instance.\n        return _this\n    }\n\n    doSum() {\n        this.sum = this.a + this.b\n    }\n}\n\nconst obj = new MyClass()\nconsole.log(obj.sum) // -\u003e 3\nobj.a = 2\nconsole.log(obj.sum) // -\u003e 4\n```\n\n```js\nconst obj = observe({\n    a: 1,\n    b: 2,\n    doSum: function() {\n        this.sum = this.a + this.b\n    }\n}, {\n    // Use the bind flag to bind doSum to the observed object.\n    bind: true\n})\n\nobj.doSum = computed(obj.doSum)\nconsole.log(obj.sum) // -\u003e 3\nobj.a = 2\nconsole.log(obj.sum) // -\u003e 4\n```\n\n## API\n\n### observe\n\nObserves an object or an array and returns a proxified version which reacts on mutations.\n\n```ts\nobserve(Object | Array, {\n    props: String[],\n    ignore: String[],\n    batch: boolean,\n    deep: boolean = true,\n    bind: boolean\n}) =\u003e Proxy\n```\n\n**Options**\n\n- `props: String[]`\n\nObserve only the properties listed.\n\n- `ignore: String[]`\n\nIgnore the properties listed.\n\n- `batch: boolean | int`\n\nBatch computed properties calls, wrapping them in a setTimeout and executing them in a new context and preventing excessive calls.\nIf batch is an integer greater than zero, the calls will be debounced by the value in milliseconds.\n\n- `deep: boolean`\n\nRecursively observe nested objects and when setting new properties.\n\n- `bind: boolean`\n\nAutomatically bind methods to the observed object.\n\n### computed\n\nWraps a function and captures observed properties which are accessed during the function execution.\nWhen those properties are mutated, the function is called to reflect the changes.\n\n```ts\ncomputed(fun: Function, {\n    autoRun: boolean,\n    callback: Function\n}) =\u003e Proxy\n```\n\n**Options**\n\n- `autoRun: boolean`\n\nIf false, will not run the function argument when calling `computed(function)`.\n\nThe computed function **must** be called **at least once** to calculate its dependencies.\n\n- `callback: Function`\n\nSpecify a callback that will be re-runned each time a dependency changes instead of the computed function.\n\n### dispose\n\nWill remove the computed function from the reactive Maps (the next time an bound observer property is called) allowing garbage collection.\n\n```ts\ndispose(Function) =\u003e void\n```\n\n### batch\n\n_Only when observables are created with the `{batch: …}` flag_\n\nWill perform accumulated b.ed computations instantly.\n\n```ts\nconst obj = observe({ a: 0, b: 0 }, { batch: true })\ncomputed(() =\u003e obj.a = obj.b)\nobj.b++\nobj.b++\nconsole.log(obj.a) // =\u003e 0\nbatch()\nconsole.log(obj.a) // =\u003e 2\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felbywan%2Fhyperactiv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felbywan%2Fhyperactiv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felbywan%2Fhyperactiv/lists"}