{"id":13447159,"url":"https://github.com/Masquerade-Circus/dragonbinder","last_synced_at":"2025-03-21T17:30:52.098Z","repository":{"id":42535568,"uuid":"177348749","full_name":"Masquerade-Circus/dragonbinder","owner":"Masquerade-Circus","description":"1kb progressive state management library inspired by Vuex.","archived":false,"fork":false,"pushed_at":"2023-02-04T14:11:18.000Z","size":2538,"stargazers_count":74,"open_issues_count":8,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-08T00:25:12.244Z","etag":null,"topics":["documentation","dragonbinder","eslint","flux","manager","npm","progressive","reactive","redux","state","state-management","store","vuex","web-app"],"latest_commit_sha":null,"homepage":"https://masquerade-circus.github.io/dragonbinder/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Masquerade-Circus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["Masquerade-Circus"],"custom":["https://www.paypal.me/masqueradecircus"]}},"created_at":"2019-03-23T23:00:40.000Z","updated_at":"2024-11-30T05:49:44.000Z","dependencies_parsed_at":"2023-02-18T17:01:26.483Z","dependency_job_id":null,"html_url":"https://github.com/Masquerade-Circus/dragonbinder","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fdragonbinder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fdragonbinder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fdragonbinder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Masquerade-Circus%2Fdragonbinder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Masquerade-Circus","download_url":"https://codeload.github.com/Masquerade-Circus/dragonbinder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244838069,"owners_count":20518778,"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":["documentation","dragonbinder","eslint","flux","manager","npm","progressive","reactive","redux","state","state-management","store","vuex","web-app"],"created_at":"2024-07-31T05:01:09.723Z","updated_at":"2025-03-21T17:30:51.735Z","avatar_url":"https://github.com/Masquerade-Circus.png","language":"JavaScript","funding_links":["https://github.com/sponsors/Masquerade-Circus","https://www.paypal.me/masqueradecircus"],"categories":["JavaScript"],"sub_categories":[],"readme":"[![npm version](https://img.shields.io/npm/v/dragonbinder.svg?style=flat)](https://npmjs.org/package/dragonbinder \"View this project on npm\")\n![](https://img.shields.io/bundlephobia/min/dragonbinder.svg?style=flat)\n![](https://img.shields.io/bundlephobia/minzip/dragonbinder.svg?style=flat)\n\n[![Build Status](https://travis-ci.org/Masquerade-Circus/dragonbinder.svg?branch=master)](https://travis-ci.org/Masquerade-Circus/dragonbinder)\n![](https://img.shields.io/github/issues/masquerade-circus/dragonbinder.svg)\n![](https://img.shields.io/snyk/vulnerabilities/npm/dragonbinder.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/521f72fc6d61426783692b62d64a3643)](https://www.codacy.com/app/Masquerade-Circus/dragonbinder?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=Masquerade-Circus/dragonbinder\u0026utm_campaign=Badge_Grade)\n[![Coverage Status](https://coveralls.io/repos/github/Masquerade-Circus/dragonbinder/badge.svg?branch=master)](https://coveralls.io/github/Masquerade-Circus/dragonbinder?branch=master)\n[![License](https://img.shields.io/github/license/masquerade-circus/dragonbinder.svg)](https://github.com/masquerade-circus/dragonbinder/blob/master/LICENSE)\n\n# Dragonbinder\n\n1kb progressive state management library inspired by Vuex.\n\n## Features\n\n-   [x] Immutable state.\n-   [x] Getters.\n-   [x] Mutations.\n-   [x] Actions.\n-   [x] Event listeners.\n-   [x] Nested modules.\n-   [x] Plugin system.\n\n## Table of Contents\n\n- [Dragonbinder](#dragonbinder)\n  - [Features](#features)\n  - [Table of Contents](#table-of-contents)\n  - [Install](#install)\n  - [Use](#use)\n    - [State](#state)\n    - [Getters](#getters)\n    - [Mutations](#mutations)\n    - [Actions](#actions)\n    - [Events](#events)\n      - [Event types](#event-types)\n    - [Nested modules](#nested-modules)\n      - [Local and root state](#local-and-root-state)\n    - [Plugin system](#plugin-system)\n      - [Using plugins](#using-plugins)\n      - [Developing plugins](#developing-plugins)\n  - [API](#api)\n  - [Contributing](#contributing)\n  - [Development, Build and Tests](#development-build-and-tests)\n  - [Legal](#legal)\n\n## Install\n\nYou can get this library as a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/):\n\n```bash\n// With npm\n$ npm install dragonbinder\n// With yarn\n$ yarn add dragonbinder\n```\n\nOr you can use it standalone in the browser with: \n`\u003cscript src=\"https://cdn.jsdelivr.net/npm/dragonbinder\"\u003e\u003c/script\u003e`\n\n## Use\n\n```javascript\nconst Dragonbinder = require('dragonbinder');\n\nconst store = new Dragonbinder({\n  state: {\n    count: 0\n  },\n  mutations: {\n    increment(state) {\n      state.count++\n    }\n  }\n});\n\nstore.commit('increment');\nconsole.log(store.state.count) // -\u003e 1\n```\n\n### State\n\n**Dragonbinder** use Proxies to create a state as a \"single source of truth\" which cannot be changed unless you commit a mutation. \nThis means that you cannot delete, modify or add a property directly. This allow us to keep track of all changes we made to the state.\n\nIf you don't provide an initial state by the `state` property **Dragonbinder** will create one.\n\n```javascript\nconst store = new Dragonbinder({\n  state: {\n    count: 0\n  },\n  mutations: {\n    addProperty(state, value) {\n      state.hello = 'world';\n    },\n    modifyProperty(state) {\n      state.count++\n    },\n    removeProperty(state) {\n      delete state.count;\n    }\n  }\n});\n\n// This will throw errors\nstore.state.hello = 'world';\nstore.state.count++;\ndelete state.count;\n\n// This will work as expected\nstore.commit('addProperty');\nstore.commit('modifyProperty');\nstore.commit('removeProperty');\n```\n\nAlso, if you want to avoid singletons to reuse your initial store definition, you can declare its state as a factory function.\n\n```javascript\nconst myStoreDefinition = {\n  state(){\n    return {\n      count: 1\n    }\n  },\n  mutations: {\n    increment(state, payload) {\n      state.count = state.count + payload;\n    }\n  }\n};\n\nconst store1 = new Dragonbinder(myStoreDefinition);\nconst store2 = new Dragonbinder(myStoreDefinition);\n\nstore1.commit('increment', 5);\nstore2.commit('increment', 3);\n\nconsole.log(store1.state.count); // -\u003e 6\nconsole.log(store2.state.count); // -\u003e 4\n```\n\n### Getters\n\nAs with Vue, with **Dragonbinder** you can create getters to create computed properties based on the state. \nThis getters will receive the state as first argument and all other getters as second.\n\n```javascript\nconst store = new Dragonbinder({\n  state: {\n    todos: [\n      {\n        content: 'First',\n        completed: false\n      }, \n      {\n        content: 'Second',\n        completed: true\n      }\n    ]\n  },\n  getters: {\n    completed(state){\n      return state.todos.filter(item =\u003e item.completed);\n    },\n    completedCount(state, getters){\n      return getters.completed.length;\n    }\n  }\n});\n\nconsole.log(store.getters.completed); // -\u003e { content: 'Second', completed: true }\nconsole.log(store.getters.completedCount); // -\u003e 1\n```\n\n### Mutations\n\nMutations are the only way to change the state and you must consider the next points when designing mutations.\n\n-   Following the Vuex pattern, mutations must be synchronous.\n-   Unlike many other libraries you can pass any number of arguments to a mutation.\n-   With **Dragonbinder** the state is deep frozen using `Object.freeze` to prevent direct changes. So, when you are changing the state by using a mutation, you can add, modify or delete only first level properties, second level properties will be read only.\n\n```javascript\nconst store = new Dragonbinder({\n  state: {\n    hello: {\n      name: 'John Doe'\n    }\n  },\n  mutations: {\n    changeNameError(state, payload){\n      state.hello.name = payload;\n    },\n    changeNameOk(state, payload){\n      state.hello = {...state.hello, name: payload};\n    },\n    changeNameTo(state, ...args){\n      state.hello = {...state.hello, name: args.join(' ')};\n    }\n  }\n});\n\n// This will throw an assign to read only property error\nstore.commit('changeNameError', 'Jane Doe');\n\n// This will work as expected\nstore.commit('changeNameOk', 'Jane Doe');\n\n// You can pass any number of arguments as payload\nstore.commit('changeNameTo', 'Jane', 'Doe');\n```\n\n### Actions\n\nIf you need to handle async functions you must use actions. And actions will always return a promise as result of calling them. \n\n```javascript\nconst store = new Dragonbinder({\n  state: {\n    count: 0\n  },\n  mutations: {\n    increment(state) {\n      state.count++\n    }\n  },\n  actions: {\n    increment(state){\n      return new Promise((resolve) =\u003e {\n        setTimeout(() =\u003e {\n          store.commit('increment');\n          resolve();\n        }, 1000);\n      })\n    }\n  }\n});\n\nstore.dispatch('increment').then(() =\u003e console.log(store.state.count)); // -\u003e 1 after one second\n```\n\n### Events\n\nYou can register/unregister callbacks to events.\n\n```javascript\nconst store = new Dragonbinder({\n  state: {\n    count: 0\n  },\n  mutations: {\n    increment(state) {\n      state.count++\n    }\n  }\n});\n\n// Add a named listener\nlet namedListener = (store, prop, newVal, oldVal) =\u003e console.log(`The property ${prop} was changed from ${oldVal} to ${newVal}`);\nstore.on('set', namedListener);\n\n// Add an anonymous listener\nlet removeAnonymousListener = store.on('set', () =\u003e console.log('Anonymous listener triggered'));\n\n// Committing increment will trigger the listener\nstore.commit('increment');\n// $ The property count was changed from 0 to 1\n// $ Anonymous listener triggered\n\n// Remove a named listener \nstore.off('set', namedListener);\n\n// Remove an anonyous listener \nremoveAnonymousListener();\n\n// Committing increment will do nothing as the listeners are already removed\nstore.commit('increment'); \n```\n\n#### Event types\n\nAll events receive the store instance as the first argument.\n\n| Event name       | Its called when                                                                          | Arguments received by place                                                    |\n| ---------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| addlistener      | An event listener is added                                                               | Event name \\| Listener added                                                   |\n| removelistener   | An event listener is removed                                                             | Event name \\| Listener removed                                                 |\n| set              | A property of the state is added or modified, also triggered when a module is registered | Property name \\| New value \\| Old value                                        |\n| delete           | A property of the state is deleted, also triggered when a module is unregistered         | Property name \\| Old value                                                     |\n| beforecommit     | Commit method called and before apply the mutation                                       | Mutation name \\| (...) Arguments passed to the mutation                        |\n| commit           | Commit method called and after apply the mutation                                        | Mutation name \\| (...) Arguments passed to the mutation                        |\n| beforedispatch   | Dispatch method called and before apply the action                                       | Action name \\| (...) Arguments passed to the action                            |\n| dispatch         | Dispatch method called and after apply the action                                        | Action name \\| (...) Arguments passed to the action                            |\n| getter           | A getter is called                                                                       | Getter name \\| Value of the getter                                             |\n| plugin           | A plugin is added                                                                        | Plugin added \\| (...) Options passed to the plugin                             |\n| registerModule   | A module is registered                                                                   | Namespace registered \\| Module definition \\| Store created with the definition |\n| unregisterModule | A module is unregistered                                                                 | Namespace unregistered \\| Store created with the definition                    |\n\n### Nested modules\n\nLike Vuex, **Dragonbinder** allows you to divide your store into modules and each module can contain its own store definition including more nested modules.\n\n```javascript\nconst moduleA = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... }\n}\n\nconst moduleB = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... }\n  modules: {\n    a: moduleA\n  }\n}\n\nconst store = new Dragonbinder({\n  modules: {\n    b: moduleB\n  }\n});\n\nconsole.log(store.state.b) // -\u003e `moduleB`'s state\nconsole.log(store.state['b.a']) // -\u003e `moduleA`'s state\n```\n\nAlso, after the store is created you can register/unregister modules with the `registerModule` and `unregisterModule` methods.\n\nConsider that when you unregister a module, only its initial nested modules will be unregistered with it.\n\n```javascript\nconst moduleA = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... }\n}\n\nconst moduleB = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... },\n  modules: {\n    a: moduleA\n  }\n}\n\nconst moduleC = {\n  state: { ... },\n  mutations: { ... },\n  actions: { ... },\n  getters: { ... }\n}\n\nconst store = new Dragonbinder();\nstore.registerModule('b', moduleB);\nstore.registerModule('b.c', moduleC);\n\nconsole.log(store.state.b) // -\u003e `moduleB`'s state\nconsole.log(store.state['b.a']) // -\u003e `moduleA`'s state\nconsole.log(store.state['b.c']) // -\u003e `moduleC`'s state\n\nstore.unregisterModule('b'); \n\nconsole.log(store.state.b) // -\u003e undefined\nconsole.log(store.state['b.a']) // -\u003e undefined\nconsole.log(store.state['b.c']) // -\u003e `moduleC`'s state\n```\n\n#### Local and root state\n\nEach module will behave like any other store, but, unlike Vuex, all **Dragonbinder** modules are namespaced by design. There is no option to add root mutations, actions or getters with a module. So, when you call a module mutation, action or getter, you need to supply its full namespace.\n\nThe first argument for mutations and getters will continue to be the local state, and with actions the first argument will be the local context/store.\n\nGetters will get the root state and root getters as the third and fourth arguments. \n\nActions will access the root context by the `rootStore` property of the local context.\n\n```javascript\nconst moduleA = {\n  state: {\n    hello: 'world'\n  },\n  mutations: {\n    sayHello(state, payload){\n      state.hello = payload;\n    }\n  },\n  actions:{\n    change(store, payload){\n      store.commit('sayHello', payload);\n      store.rootStore.commit('increment');\n    }\n  },\n  getters: {\n    hello(state, getters, rootState, rootGetters){\n      return `You have said hello ${rootState.count} times to ${state.hello}`;\n    }\n  }\n};\n\nconst store = new Dragonbinder({\n  state: {\n    count: 0\n  },\n  mutations: {\n    increment(state){\n      state.count++;\n    }\n  },\n  modules: {\n    a: moduleA\n  }\n});\n\nstore.dispatch('a.change', 'John Doe');\nconsole.log(store.getters['a.hello']); // -\u003e You have said hello 1 times to John Doe\nconsole.log(store.state.count) // -\u003e 1\nconsole.log(store.state.a.hello) // -\u003e John Doe\n```\n\n### Plugin system\n\n**Dragonbinder** comes with a simple but powerfull plugin system.\nYou can extend its core functionality or change it completely by making use of plugins. \n\n#### Using plugins\n\n```javascript\nlet store = new Dragonbinder();\nstore.use(myPlugin, ...options);\n```\n\n#### Developing plugins\n\nA **Dragonbinder** plugin is a module that exports a single function that will be called\nwith the store instance as first argument and optionally with the passed options if any.\n\n```javascript\nconst Dragonbinder = require('dragonbinder');\nconst myPlugin = (store, ...options) =\u003e {\n\n  Dragonbinder.myGlobalMethod = function() {\n    // Awesome code here\n  };\n  \n  Dragonbinder.fn.myPrototypeMethod = function() {\n    // Awesome code here\n  };\n\n  store.myLocalMethod = function() {\n    // Awesome code here\n  };\n};\n```\n\n## API\n\nCheck the docs at: \u003chttps://masquerade-circus.github.io/dragonbinder/?api\u003e\n\n## Contributing\n\nCheck the contributing guide at: \u003chttps://masquerade-circus.github.io/dragonbinder/?content=contributing\u003e\n\n## Development, Build and Tests\n\n-   Use `yarn dev` to watch and compile the library on every change to it. \n-   Use `yarn build` to build the library. \n-   Use `yarn test` to run tests only once.\n-   Use `yarn dev:test` to run the tests watching changes to library and tests.\n-   Use `yarn dev:test:nyc` to run the tests watching changes and get the test coverage at last.\n-   Use `yarn docs` to build the documentation. \n-   Use `yarn docs:watch` to watch and rebuild the documentation on every change to the library or the md files.\n-   Use `yarn docs:serve` to see the generated documentation locally. \n\n## Legal\n\nAuthor: [Masquerade Circus](http://masquerade-circus.net). License [Apache-2.0](https://opensource.org/licenses/Apache-2.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMasquerade-Circus%2Fdragonbinder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMasquerade-Circus%2Fdragonbinder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMasquerade-Circus%2Fdragonbinder/lists"}