{"id":17624717,"url":"https://github.com/neuronetio/deep-state-observer","last_synced_at":"2025-10-28T14:46:38.108Z","repository":{"id":46806814,"uuid":"204911980","full_name":"neuronetio/deep-state-observer","owner":"neuronetio","description":"State library for high performance applications.","archived":false,"fork":false,"pushed_at":"2025-02-11T14:35:55.000Z","size":978,"stargazers_count":32,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-22T15:53:09.200Z","etag":null,"topics":["backend","deep-state","deep-storage","deep-store","frontend","observer","params","reactive","state","store","subscribe","wildcard","wildcards"],"latest_commit_sha":null,"homepage":"https://neuronetio.github.io/deep-state-observer/","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/neuronetio.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-28T11:04:21.000Z","updated_at":"2025-02-11T14:35:59.000Z","dependencies_parsed_at":"2025-02-21T20:10:21.620Z","dependency_job_id":"76b80ad2-845d-4ef9-bdb8-61b87fcd9852","html_url":"https://github.com/neuronetio/deep-state-observer","commit_stats":{"total_commits":493,"total_committers":2,"mean_commits":246.5,"dds":0.006085192697768749,"last_synced_commit":"7c2b99516b51a1c60878af78954712ad24b828f2"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/neuronetio/deep-state-observer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuronetio%2Fdeep-state-observer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuronetio%2Fdeep-state-observer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuronetio%2Fdeep-state-observer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuronetio%2Fdeep-state-observer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neuronetio","download_url":"https://codeload.github.com/neuronetio/deep-state-observer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuronetio%2Fdeep-state-observer/sbom","scorecard":{"id":681467,"data":{"date":"2025-08-11","repo":{"name":"github.com/neuronetio/deep-state-observer","commit":"17d3c7c95e4ee701cbdb58ad8c1cbcb358bde7d0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab 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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: wildcard_matcher_bg.wasm:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T23:18:51.091Z","repository_id":46806814,"created_at":"2025-08-21T23:18:51.091Z","updated_at":"2025-08-21T23:18:51.091Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272013537,"owners_count":24858474,"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-08-25T02:00:12.092Z","response_time":1107,"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":["backend","deep-state","deep-storage","deep-store","frontend","observer","params","reactive","state","store","subscribe","wildcard","wildcards"],"created_at":"2024-10-22T22:05:04.465Z","updated_at":"2025-10-28T14:46:33.061Z","avatar_url":"https://github.com/neuronetio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![GitHub license](https://img.shields.io/github/license/neuronetio/deep-state-observer?style=flat-square)](https://github.com/neuronetio/deep-state-observer/blob/master/LICENSE)\n[![GitHub issues](https://img.shields.io/github/issues/neuronetio/deep-state-observer)](https://github.com/neuronetio/deep-state-observer/issues)\n[![Twitter](https://img.shields.io/twitter/url/https/github.com/neuronetio/deep-state-observer)](https://twitter.com/intent/tweet?text=Wow:\u0026url=https%3A%2F%2Fgithub.com%2Fneuronetio%2Fdeep-state-observer)\n\n# deep-state-observer for high performance apps\n\nDeep state observer is an state management library which will trigger an update only when specified object node was changed.\nYou don't need to reevaluate or re-render whole app/component when only one portion of the state was modified.\n\n### Used in the right hands can significantly increase performance!\n\nDeep state observer is framework agnostic with node and browser support, so you can use it in most of your projects.\n\n# Install\n\n`npm i deep-state-observer`\n\n# Examples\n\n[here](https://svelte.dev/repl/a3637b5e83914c9b89f10c8cd422c747?version=3.12.1) and [here](https://svelte.dev/repl/3e9f95108e5f44c0921b1c3fab8574e7?version=3.12.1)\n\n# Usage\n\n## Svelte example\n\n```javascript\nimport { onDestroy } from 'svelte';\nimport State from 'deep-state-observer'; // const State = require('deep-state-observer');\n\n// first parameter is an object that hold the state, and the second one is just options\nconst state = new State({\n  some: 'value',\n  someOther: {\n      nested: 'value'\n    }\n  },\n  // options\n  { delimiter:'.' , notRecursive:';',  param: ':', log: console.log }\n);\n\n// store some unsubscribe methods\nlet subscribers = [];\n\n// change local variable - it can be vueComponent.data property or react function with setState in react\nlet nestedValue;\nsubscribers.push(\n  state.subscribe('someOther.nested', (value, eventInfo) =\u003e {\n    nestedValue = value;\n  })\n);\n\nlet some;\nsubscribers.push(\n  state.subscribeAll(['some', 'someOther'], (value, eventInfo) =\u003e {\n    if (eventInfo.path.resolved === 'some') {\n      some = value;\n    } else if (eventInfo.path.resolved === 'someOther') {\n      nestedValue = value.nested;\n    }\n  })\n);\n\nstate.update('someOther.nested', (currentValue) =\u003e {\n  return 'new value';\n});\n\n// you can use function to modify data\nsubscribers.push(\n  state.subscribe('some', (value, eventInfo) =\u003e {\n    state.update('someOther.nested', (oldValue) =\u003e {\n      return 'nested changed too';\n    });\n  })\n);\n\n// or you can just set the value (it cannot be function :) )\nsubscribers.push(\n  state.subscribe('some', (value, eventInfo) =\u003e {\n    state.update('someOther.nested', 'nested changed too');\n);\n\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Wildcards\n\n```javascript\nimport { onDestroy } from \"svelte\";\nimport State from \"deep-state-observer\"; // const State = require('deep-state-observer');\n\n// first parameter is an object that hold the state, and the second one is just options (optional - for now it hold just delimiter :P )\nconst state = new State({\n  some: { thing: { test: 0 } },\n  someOther: { nested: { node: \"ok\" } },\n});\n\n// store some unsubscribe methods\nlet subscribers = [];\n\nsubscribers.push(\n  state.subscribe(\"someOther.*.n*e\", (value, eventInfo) =\u003e {\n    // fired only once with\n    // value = 'ok'\n    // eventInfo.path.resolved = 'someOther.nested.node'\n  })\n);\n\n// you can update wildcarded values too\nstate.update(\"some.*.test\", \"test\");\n\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Named wildcards (parameters)\n\n```javascript\nimport { onDestroy } from \"svelte\";\nimport State from \"deep-state-observer\"; // const State = require('deep-state-observer');\n\n// first parameter is an object that hold the state, and the second one is just options (optional - for now it hold just delimiter :P )\nconst state = new State({\n  items: [{ val: 1 }, { val: 2 }, { val: 3 }],\n  byId: {\n    1: { val: 1 },\n    2: { val: 2 },\n    3: { val: 3 },\n  },\n});\n\n// store some unsubscribe methods\nlet subscribers = [];\n\nsubscribers.push(\n  state.subscribe(\"items.:index.val\", (value, eventInfo) =\u003e {\n    // fired three times\n    //\n    // #1\n    // value = 1\n    // eventInfo.path.resolved = 'items.0.val'\n    // eventInfo.params = { index: 0 }\n    //\n    // #2\n    // value = 2\n    // eventInfo.path.resolved = 'items.1.val'\n    // eventInfo.params = { index: 1 }\n    //\n    // #3\n    // value = 3\n    // eventInfo.path.resolved = 'items.2.val'\n    // eventInfo.params = { index: 2 }\n  })\n);\n\nsubscribers.push(\n  state.subscribe(\"byId.:id.val\", (value, eventInfo) =\u003e {\n    // fired three times\n    //\n    // #1\n    // value = 1\n    // eventInfo.path.resolved = 'byId.1.val'\n    // eventInfo.params = { id: 1 }\n    //\n    // #2\n    // value = 2\n    // eventInfo.path.resolved = 'byId.2.val'\n    // eventInfo.params = { id: 2 }\n    //\n    // #3\n    // value = 3\n    // eventInfo.path.resolved = 'byId.3.val'\n    // eventInfo.params = { id: 3 }\n  })\n);\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Wildcard bulk operations (better performance)\n\n```javascript\nimport { onDestroy } from \"svelte\";\nimport State from \"deep-state-observer\"; // const State = require('deep-state-observer');\n\n// first parameter is an object that hold the state, and the second one is just options (optional - for now it hold just delimiter :P )\nconst state = new State({\n  byId: {\n    1: { val: 1 },\n    2: { val: 2 },\n    3: { val: 3 },\n  },\n});\n\n// store some unsubscribe methods\nlet subscribers = [];\n\nsubscribers.push(\n  state.subscribe(\n    \"byId.:id.val\",\n    (bulk, eventInfo) =\u003e {\n      // fired only once where bulk = [\n      //  {\n      //    value: 1,\n      //    eventInfo.path.resovled: 'byId.1.val',\n      //    eventInfo.params: { id: 1 }\n      //  },\n      //  {\n      //    value: 2,\n      //    eventInfo.path.resovled: 'byId.2.val',\n      //    eventInfo.params: { id: 2 }\n      //  },\n      //  {\n      //    value: 3,\n      //    eventInfo.path.resovled: 'byId.3.val',\n      //    eventInfo.params: { id: 3 }\n      //  },\n      // ]\n    },\n    { bulk: true }\n  )\n);\n\nsubscribers.push(\n  state.subscribe(\n    \"byId.*.val\",\n    (bulk, eventInfo) =\u003e {\n      // fired only once where bulk = [\n      //  {\n      //    value: 1,\n      //    eventInfo.path.resolved: 'byId.1.val',\n      //    eventInfo.params: undefined\n      //  },\n      //  {\n      //    value: 2,\n      //    eventInfo.path.resolved: 'byId.2.val',\n      //    eventInfo.params: undefined\n      //  },\n      //  {\n      //    value: 3,\n      //    eventInfo.path.resolved: 'byId.3.val',\n      //    eventInfo.params: undefined\n      //  },\n      // ]\n    },\n    { bulk: true }\n  )\n);\n\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Wildcard bulk without bulk values (even better performance)\n\n```javascript\nimport { onDestroy } from \"svelte\";\nimport State from \"deep-state-observer\"; // const State = require('deep-state-observer');\n\n// first parameter is an object that hold the state, and the second one is just options (optional - for now it hold just delimiter :P )\nconst state = new State({\n  byId: {\n    1: { val: 1 },\n    2: { val: 2 },\n    3: { val: 3 },\n  },\n});\n\n// store some unsubscribe methods\nlet subscribers = [];\n\nsubscribers.push(\n  state.subscribe(\n    \"byId.:id.val\",\n    (bulk, eventInfo) =\u003e {\n      // fired only once where bulk = [\n      //  {\n      //    value: undefined,\n      //    eventInfo.params: { id: 1 }\n      //  },\n      //  {\n      //    value: undefined,\n      //    eventInfo.params: { id: 2 }\n      //  },\n      //  {\n      //    value: 3,\n      //    eventInfo.params: { id: 3 }\n      //  },\n      // ]\n    },\n    { bulk: true, bulkValue: false }\n  )\n);\n\nsubscribers.push(\n  state.subscribe(\n    \"byId.*.val\",\n    (bulk, eventInfo) =\u003e {\n      // fired only once where bulk = [\n      //  {\n      //    value: undefined,\n      //    eventInfo.params: undefined\n      //  },\n      //  {\n      //    value: undefined,\n      //    eventInfo.params: undefined\n      //  },\n      //  {\n      //    value: undefined,\n      //    eventInfo.params: undefined\n      //  },\n      // ]\n    },\n    { bulk: true, bulkValue: false }\n  )\n);\n\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Observe only chosen node changes (not recursive, not nested, for immutable data)\n\n```javascript\nimport { onDestroy } from \"svelte\";\nimport State from \"deep-state-observer\"; // const State = require('deep-state-observer');\n\nconst state = new State({\n  some: \"value\",\n  someOther: { nested: { node: \"ok\" } },\n});\n\n// store some unsubscribe methods\nlet subscribers = [];\n\nsubscribers.push(\n  state.subscribe(\"someOther;\", (value, eventInfo) =\u003e {\n    // fired once\n    //\n    // #1 - immediately with\n    // value = { nested: { node: 'ok' } }\n    // eventInfo.path.resovled = 'someOther'\n  })\n);\n\nsubscribers.push(\n  state.subscribe(\"someOther.nested;\", (value, eventInfo) =\u003e {\n    // fired once\n    //\n    // #1 - immediately with\n    // value =  { node: 'ok' }\n    // eventInfo.path.resolved = 'someOther'\n  })\n);\n\nstate.update(\"someOther.nested.node\", \"modified\"); // subscribers aren't fired\n\nonDestroy(() =\u003e {\n  subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n});\n```\n\n## Update and notify only specified listeners\n\n```javascript\nconst state = new State({\n  one: { two: { three: { four: 4 } } },\n});\nstate.subscribe(\"one.two\", (val, eventInfo) =\u003e {\n  // trigerred only once - immediately\n  // because update have option 'only' wich will update only selected nested paths even i you are updating whole 'one.two' object\n  // 'only' option works for object and arrays and it is usefull when you have changed only 'four'-th node of the object\n  // and don't want to listeners from 'one.two' be notified (it is kind of performance improvement hack)\n  // you can bypass those huge operation that is executed here because it is not needed (we are changing only one 'four'th leaf)\n});\nstate.subscribe(\"one.two.three.four\", (val, eventInfo) =\u003e {\n  // trigerred two times immediately and after update\n});\nstate.update(\"one.two\", { three: { four: 44 } }, { only: [\"*.four\"] });\n```\n\n## mute / unmute changes\n\nIt will work with wildcards as update values, as muted paths or both.\n\n```javascript\nconst state = new State({ x: { z: \"z\", i: { o: \"o\" } }, y: \"y\" });\nconst values = [];\nstate.subscribe(\"x.i.o\", (val) =\u003e {\n  values.push(val);\n});\n// values.length === 1\nstate.mute(\"x.*.o\");\nstate.update(\"x.i.o\", \"oo\");\n// values.length === 1 (x.i.o listener was not fired)\nstate.unmute(\"x.*.o\");\nstate.update(\"x.i.o\", \"ooo\");\n// values.length === 2 (x.i.o listener was fired)\n\nstate.mute(\"x\");\nstate.update(\"x.i.o\", \"oooo\");\n// values.length === 2 (x.i.o listener was not fired)\n\nstate.unmute(\"x\");\nstate.mute(\"x;\"); // mute only x (not nested)\nstate.update(\"x.i.o\", \"oooo\");\n// values.length === 3 (x.i.o listener was fired)\n```\n\nYou can also mute specific listeners (functions)\n\n```javascript\nconst state = new State({ x: { z: \"z\", i: { o: \"o\" } }, y: \"y\" });\nconst values = [];\n\nfunction listener1() {\n  values.push(\"1\");\n}\nfunction listener2() {\n  values.push(\"2\");\n}\n\nstate.subscribe(\"x.i.o\", listener1);\nstate.subscribe(\"x.i.o\", listener2);\n// values = ['1', '2']\n\nstate.mute(listener2); // from now on listener2 will not fire\n\nvalues.length = 0;\nstate.update(\"x.i.o\", \"oo\");\n// values = ['1']\n\nstate.unmute(listener2);\n\nvalues.length = 0;\nstate.update(\"x.i.o\", \"oo\");\n// values = ['1','2']\n```\n\n## ignore\n\nYou can watch for object changes but also at the same time ignore specified nodes.\nIgnore option will work with wildcards.\n\n```javascript\nconst state = new State({ one: { two: { three: { four: { five: 0 } } } } });\nconst values = [];\n\nstate.subscribe(\n  \"one.two.three\",\n  (val) =\u003e {\n    values.push(val);\n  },\n  { ignore: [\"one.two.three.four\"] }\n);\n// values.length === 1 \u0026 values[0] === { four: { five: 0 } }\nstate.update(\"one.two.three.four.five\", 1);\n// values.length === 1 because all nodes after four was ignored\nstate.update(\"one.two.three.*.five\", 1);\n// values.length === 1 because all nodes after four was ignored\nstate.update(\"one.two.three.four\", 1);\n// values.length === 1 because all nodes after four was ignored\nstate.update(\"one.two.*.four\", 2);\n// values.length === 1 because all nodes after four was ignored\nstate.update(\"one.two.three\", 1);\n// values.length === 1 \u0026 values[1] === 1\n```\n\n## multi\n\nYou can collect updates and execute them at once later - useful when used with groups.\n\n```javascript\nconst state = new State({\n  x: { y: { z: { a: { b: \"b\" } } } },\n  c: { d: { e: \"e\" } },\n});\nconst values = [];\nstate.subscribe(\"x.y.z.a.b\", (val, eventInfo) =\u003e {\n  values.push(val);\n});\nstate.subscribeAll(\n  [\"x.y.*.a.b\", \"c.d.e\"],\n  (val, eventInfo) =\u003e {\n    values.push(\"all\");\n  },\n  { group: true }\n);\n// values.length === 2\n// values[0] = 'b'\n// values[1] = 'all'\n\nconst multi = state.multi(true); // true if you want to execute all changes for specific group only once otherwise each update will cause group to be fired\n// from now on collect updates but do not notify any listener yet\nmulti.update(\"x.y.z.a.b\", \"bb\");\nmulti.update(\"c.d.e\", \"ee\");\n// values.length === 2\n\nmulti.done(); // notify all listeners about updates - fire only once grouped listeners\n// values.length === 4\n// values[2] === 'bb'\n// values[3] === 'all'\n```\n\n## group - very useful if you want to join couple of changes into one\n\nWith subscribeAll you can group listeners to fire only once if one of the path is changed.\nGrouped listeners always are bulk listeners.\n\n```javascript\nconst state = new State({\n  \"n-1\": {\n    \"n-1-1\": {\n      id: \"1-1\",\n      val: \"v1-1\",\n    },\n    \"n-1-2\": {\n      id: \"1-2\",\n      val: \"v1-2\",\n    },\n  },\n});\nconst results = [];\nfunction fn(bulk, eventInfo) {\n  results.push(eventInfo.path);\n}\nstate.subscribeAll([\"n-1.n-1-1.id\", \"n-1.n-1-2.val\"], fn, {\n  group: true,\n});\n// results.length = 1\nstate.subscribeAll([\"n-1\"], fn);\n// results.length = 2  // not 3\nstate.update(\"n-1.*.id\", \"new id\");\n// results.length = 4 // not 6\nstate.update(\"n-1.*.id\", \"new id 2\");\n// results.length = 6 // not 9\n```\n\n## collect\n\nYou can start collecting changes and execute it as multi later - performance optimization for groups.\nIt is just global multi.\n\n```javascript\nconst state = new State({\n  x: { y: { z: { a: { b: \"b\" } } } },\n  c: { d: { e: \"e\" } },\n});\nconst values = [];\nstate.subscribe(\"x.y.z.a.b\", (val, eventInfo) =\u003e {\n  values.push(val);\n});\nstate.subscribeAll(\n  [\"x.y.*.a.b\", \"c.d.e\"],\n  (val, eventInfo) =\u003e {\n    values.push(\"all\");\n  },\n  { group: true }\n);\n// values.length === 2\n// values[0] = 'b'\n// values[1] = 'all'\n\nstate.collect(); // from now on collect updates but do not notify any listener yet\nstate.update(\"x.y.z.a.b\", \"bb\");\nstate.update(\"c.d.e\", \"ee\");\n// values.length === 2\n\nstate.executeCollected(); // notify all listeners about updates - fire only once grouped listeners\n// values.length === 4\n// values[2] === 'bb'\n// values[3] === 'all'\n```\n\n## Trace\n\nYou can easily track traces\n\n`state.startTrace(name: string, additionalData: any = null): string` start tracing\n\n`state.stopTrace(id:string): Trace` get current trace\n\n`state.saveTrace(id:string):Trace` save trace on the stack\n\n`state.getSavedTraces(): Trace[]` get all traces from the stack\n\n```javascript\nconst state = new State({\n  p1: \"p1v\",\n  p2: \"p2v\",\n  x1: \"x1v\",\n  x2: \"x2v\",\n});\nstate.subscribe(\"p1\", (val, eventInfo) =\u003e {\n  const trackId = state.startTrace(\"p1\", eventInfo);\n  state.update(\"p2\", \"p2v-\");\n  state.saveTrace(trackId);\n});\nstate.subscribe(\"p2\", (val, eventInfo) =\u003e {\n  const trackId = state.startTrace(\"p2\", eventInfo);\n  state.update(\"x2\", \"x2v-\");\n  state.saveTrace(trackId);\n});\nconst result = state.getSavedTraces();\nconsole.log(result);\n```\n\n## Debug\n\n```javascript\n// you can debug listeners and updates with 'debug' and 'source' options\nstate.subscribe(\"something\", () =\u003e {}, {\n  debug: true,\n  source: \"your.component.name.or.something\",\n});\nstate.update(\"something\", \"someValue\", {\n  debug: true,\n  source: \"your.component.name.or.something\",\n});\n```\n\n## Vue example\n\n```javascript\n// main component\nimport State from 'deep-state-observer';\n\nconst state = new State({ test: 1 });\nconst subscribers = [];\n\nexport default {\n  provide: { state },\n};\n\n// child component\nexport default {\n  template: `\u003cdiv\u003etest is equal to: {{test}}\u003c/div\u003e`,\n  inject: ['state'],\n  data() {\n    return {\n      test: 0,\n    };\n  },\n  created() {\n    subscribers.push(\n      this.state.subscribe('test', (test) =\u003e {\n        this.test = test; // assign to local variable\n      })\n    );\n  },\n  beforeDestroy() {\n    subscribers.forEach((unsubscribe) =\u003e unsubscribe());\n  },\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuronetio%2Fdeep-state-observer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneuronetio%2Fdeep-state-observer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuronetio%2Fdeep-state-observer/lists"}