├── .codeclimate.yml ├── .eslintignore ├── .eslintrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.js ├── dist ├── dragonbinder.js ├── dragonbinder.min.js ├── dragonbinder.min.js.map └── dragonbinder.mjs ├── docma.json ├── docs ├── content │ ├── contributing.html │ └── readme.html ├── css │ ├── docma.css │ └── styles.css ├── img │ ├── tree-deep.png │ ├── tree-first.png │ ├── tree-folded.png │ ├── tree-last.png │ ├── tree-node.png │ ├── tree-parent.png │ └── tree-space.png ├── index.html └── js │ ├── app.min.js │ ├── docma-web.js │ ├── highlight.pack.js │ └── tippy.all.min.js ├── lib └── index.js ├── package-lock.json ├── package.json ├── rollup.js └── tests ├── index_test.js ├── test.cjs ├── test.min.js └── test.mjs /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: '2' # required to adjust maintainability checks 2 | checks: 3 | argument-count: 4 | config: 5 | threshold: 10 6 | complex-logic: 7 | config: 8 | threshold: 4 9 | file-lines: 10 | config: 11 | threshold: 500 12 | method-complexity: 13 | config: 14 | threshold: 20 15 | method-count: 16 | config: 17 | threshold: 20 18 | method-lines: 19 | config: 20 | threshold: 250 21 | nested-control-flow: 22 | config: 23 | threshold: 6 24 | return-statements: 25 | config: 26 | threshold: 4 27 | exclude_patterns: 28 | - 'dist' 29 | - 'tests' 30 | - 'docs' 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["sonarjs"], 3 | "extends": ["plugin:sonarjs/recommended"], 4 | "rules": { 5 | "array-bracket-spacing": 2, 6 | "brace-style": 2, 7 | "comma-dangle": 2, 8 | "comma-spacing": 2, 9 | "comma-style": 2, 10 | "curly": 2, 11 | "dot-notation": 2, 12 | "eol-last": 2, 13 | "eqeqeq": ["error", "smart"], 14 | "key-spacing": 2, 15 | "keyword-spacing": 2, 16 | "new-cap": 0, 17 | "no-empty": [2, { "allowEmptyCatch": true }], 18 | "no-eval": 2, 19 | "no-implied-eval": 2, 20 | "no-mixed-spaces-and-tabs": 2, 21 | "no-multi-str": 2, 22 | "no-sequences": 2, 23 | "no-trailing-spaces": 2, 24 | "no-underscore-dangle": 2, 25 | "no-use-before-define": 2, 26 | "no-with": 2, 27 | "semi-spacing": 2, 28 | "space-before-blocks": 2, 29 | "space-before-function-paren": [ 30 | 2, 31 | { "anonymous": "always", "named": "never" } 32 | ], 33 | "space-in-parens": 2, 34 | "space-infix-ops": 2, 35 | "space-unary-ops": 2, 36 | "vars-on-top": 2, 37 | "wrap-iife": 2, 38 | "semi": [2, "always"], 39 | "indent": [ 40 | 2, 41 | 2, 42 | { 43 | "SwitchCase": 1 44 | } 45 | ], 46 | "valid-jsdoc": 2, 47 | "no-var": "error", 48 | "no-undef": 1, 49 | "no-multi-spaces": "error", 50 | "no-multiple-empty-lines": "error", 51 | "one-var": "off", 52 | "no-continue": "off", 53 | "no-new-func": "warn", 54 | "no-plusplus": "off", 55 | "no-console": "warn", 56 | "complexity": ["warn", 20], 57 | "sonarjs/cognitive-complexity": ["warn", 20], 58 | "max-lines-per-function": [ 59 | "warn", 60 | { "max": 300, "skipBlankLines": true, "skipComments": true } 61 | ], 62 | "max-len": ["error", 120], 63 | "max-statements-per-line": ["error", { "max": 3 }] 64 | }, 65 | "env": { 66 | "shared-node-browser": true, 67 | "browser": true, 68 | "node": true, 69 | "es6": true 70 | }, 71 | "parserOptions": { 72 | "ecmaVersion": 9, 73 | "sourceType": "module", 74 | "ecmaFeatures": { 75 | "jsx": true, 76 | "modules": true 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Masquerade-Circus] 4 | custom: ["https://www.paypal.me/masqueradecircus"] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 4 | 5 | Resolves #(issue) 6 | 7 | ## Type of change 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] This change requires a documentation update 13 | - [ ] Documentation update 14 | 15 | # How Has This Been Tested? 16 | 17 | Please describe the tests that you ran to verify your changes. 18 | 19 | - [ ] Test A 20 | - [ ] Test B 21 | 22 | # Checklist: 23 | 24 | - [ ] My code follows the style guidelines of this project 25 | - [ ] I have performed a self-review of my own code 26 | - [ ] I have commented my code, particularly in hard-to-understand areas 27 | - [ ] I have made corresponding changes to the documentation 28 | - [ ] My changes generate no new warnings 29 | - [ ] I have added tests that prove my fix is effective or that my feature works 30 | - [ ] New and existing unit tests pass locally with my changes 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - yarn 8 | 9 | script: 10 | - yarn test 11 | 12 | after_success: yarn coverage -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### [2.2.1](https://github.com/Masquerade-Circus/dragonbinder/compare/v2.2.0...v2.2.1) (2022-07-05) 4 | 5 | 6 | ### Miscellaneous Chores 7 | 8 | * Update dependencies and build process ([4c2b938](https://github.com/Masquerade-Circus/dragonbinder/commit/4c2b93881790df974911f7ecc8c74346aea55da7)) 9 | 10 | 11 | ### Build System 12 | 13 | * add conventional-changelog and release-it ([cba89a2](https://github.com/Masquerade-Circus/dragonbinder/commit/cba89a2a8b06059b36a04ec210d3f94487888638)) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at christian@masquerade-circus.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | ## Table of Contents 4 | 5 | - [Contributing Guide](#contributing-guide) 6 | - [Table of Contents](#table-of-contents) 7 | - [Ways to contribute](#ways-to-contribute) 8 | - [Call for contributors](#call-for-contributors) 9 | - [Submit Feedback](#submit-feedback) 10 | - [Fix Bugs](#fix-bugs) 11 | - [Implement Features](#implement-features) 12 | - [Write Documentation](#write-documentation) 13 | - [Develop a plugin](#develop-a-plugin) 14 | - [Pull Request Guide](#pull-request-guide) 15 | - [Code Style Guide](#code-style-guide) 16 | - [Documentation Guide](#documentation-guide) 17 | - [Testing Guide](#testing-guide) 18 | - [Attribution](#attribution) 19 | 20 | ## Ways to contribute 21 | 22 | ### Call for contributors 23 | 24 | You can follow the tag of `call for contributors` in the issues. The pull requests solving these issues are most likely to be merged. 25 | 26 | ### Submit Feedback 27 | 28 | The feedback should be submitted by creating an issue at GitHub issues. Select the related template and add the corresponding labels. 29 | 30 | ### Fix Bugs 31 | 32 | You may look through the GitHub issues for bugs. Anything tagged with `bug report` is open to whoever wants to implement it. 33 | 34 | ### Implement Features 35 | 36 | You may look through the GitHub issues for feature requests. Anything tagged with `feature request` is open to whoever wants to implement it. 37 | 38 | ### Write Documentation 39 | 40 | The documentation is either directly written into the Markdown files, or automatically extracted from the `jsdoc comments` by executing the `yarn docs` script, this will make use of [docma](https://onury.io/docma/) to generate the documentation. 41 | 42 | In the first situation, you only need to change the markdown file and if its a new file, add it to the `docma.json` configuration. 43 | 44 | In the second situation, you need to change the `jsdoc comments` of the file that needs the documentation. 45 | 46 | ### Develop a plugin 47 | 48 | You may develop a plugin creating your own repo. Just give your plugin a meaningful unique name, make sure the name follows the convention dragonbinder-plugin-{name} and, when finished, you can make a [pull request](#pull-request-guide) updating the documentation with your plugin listed in the Plugin system section. 49 | 50 | ## Pull Request Guide 51 | 52 | Before you submit a pull request, check that it meets these guidelines. Please also read [Code Style Guide](#code-style-guide), [Documentation Guide](#documentation-guide) and [Testing Guide](#testing-guide) to ensure your merge request meet our requirements. 53 | 54 | - Fork the repository. Create a new branch from the master branch. Give your new branch a meaningful name. 55 | - Create a [draft pull request](https://help.github.com/en/articles/about-pull-requests#draft-pull-requests) from your new branch to the master branch of the original repo. Give your pull request a meaningful name. 56 | - Include `resolves #issue_number` in the description of the pull request if needed and briefly describe your contribution. 57 | - Submit the pull request from the first day of your development (after your first commit). 58 | - When the contribution is complete, make sure the pull request passed the CI tests. Change the draft pull request status to ready for review. Set the reviewer to [@masquerade-circus](https://github.com/Masquerade-Circus). 59 | - For the case of bug fixes, add new test cases which would fail before your bug fix. 60 | 61 | ## Code Style Guide 62 | 63 | This project use [eslint](https://eslint.org/) with [sonarjs eslint plugin](https://github.com/SonarSource/eslint-plugin-sonarjs) and [prettier](https://prettier.io/) to format the code. Make sure you have configured your editor to format the files using this rules on save. Consider that `jsdoc comments` are not mandatory, but we need them to build a trustworthy documentation. 64 | 65 | ## Documentation Guide 66 | 67 | The documentation should be provided in two ways, `jsdoc comments` and updating the readme file. We prefer the documentation to be as complete as possible. 68 | 69 | You only need to add tutorials to your code if you are contributing or updating a new feature. 70 | 71 | If you are updating via jsdoc comments or updating the readme file, you can make use of the `yarn docs:watch` script to watch for changes to the lib and the readme file. This script will rebuild the documentation on every change. 72 | 73 | To see the documentation locally, you can make use of the `yarn docs:serve`, this will serve the documentation at . 74 | 75 | Finally, before commit make use of [remark](https://www.npmjs.com/package/remark) to format any updated markdown file. After format the updated files, build the documentation one last time using the `yarn docs` script. 76 | 77 | ## Testing Guide 78 | 79 | We make use of [AVA](https://github.com/avajs/ava) to write tests. You should test your code by writing unit testing code in tests directory. The testing file name should be a {feature}\_test.js file in the corresponding directory. The suffix \_test is mandatory. 80 | 81 | You can make use of the `yarn dev:test:nyc` script to watch for changes and run the tests on every change. It would output the coverage information to the console and into a directory named coverage. Please make sure the code coverage percentage does not decrease after your contribution, otherwise, the code will not be merged. 82 | 83 | ## Attribution 84 | 85 | This contribution guide is adapted partially from the [Auto-Keras contributing guide](https://autokeras.com/temp/contribute/). 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/dragonbinder.svg?style=flat)](https://npmjs.org/package/dragonbinder "View this project on npm") 2 | ![](https://img.shields.io/bundlephobia/min/dragonbinder.svg?style=flat) 3 | ![](https://img.shields.io/bundlephobia/minzip/dragonbinder.svg?style=flat) 4 | 5 | [![Build Status](https://travis-ci.org/Masquerade-Circus/dragonbinder.svg?branch=master)](https://travis-ci.org/Masquerade-Circus/dragonbinder) 6 | ![](https://img.shields.io/github/issues/masquerade-circus/dragonbinder.svg) 7 | ![](https://img.shields.io/snyk/vulnerabilities/npm/dragonbinder.svg) 8 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/521f72fc6d61426783692b62d64a3643)](https://www.codacy.com/app/Masquerade-Circus/dragonbinder?utm_source=github.com&utm_medium=referral&utm_content=Masquerade-Circus/dragonbinder&utm_campaign=Badge_Grade) 9 | [![Coverage Status](https://coveralls.io/repos/github/Masquerade-Circus/dragonbinder/badge.svg?branch=master)](https://coveralls.io/github/Masquerade-Circus/dragonbinder?branch=master) 10 | [![License](https://img.shields.io/github/license/masquerade-circus/dragonbinder.svg)](https://github.com/masquerade-circus/dragonbinder/blob/master/LICENSE) 11 | 12 | # Dragonbinder 13 | 14 | 1kb progressive state management library inspired by Vuex. 15 | 16 | ## Features 17 | 18 | - [x] Immutable state. 19 | - [x] Getters. 20 | - [x] Mutations. 21 | - [x] Actions. 22 | - [x] Event listeners. 23 | - [x] Nested modules. 24 | - [x] Plugin system. 25 | 26 | ## Table of Contents 27 | 28 | - [Dragonbinder](#dragonbinder) 29 | - [Features](#features) 30 | - [Table of Contents](#table-of-contents) 31 | - [Install](#install) 32 | - [Use](#use) 33 | - [State](#state) 34 | - [Getters](#getters) 35 | - [Mutations](#mutations) 36 | - [Actions](#actions) 37 | - [Events](#events) 38 | - [Event types](#event-types) 39 | - [Nested modules](#nested-modules) 40 | - [Local and root state](#local-and-root-state) 41 | - [Plugin system](#plugin-system) 42 | - [Using plugins](#using-plugins) 43 | - [Developing plugins](#developing-plugins) 44 | - [API](#api) 45 | - [Contributing](#contributing) 46 | - [Development, Build and Tests](#development-build-and-tests) 47 | - [Legal](#legal) 48 | 49 | ## Install 50 | 51 | You can get this library as a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/): 52 | 53 | ```bash 54 | // With npm 55 | $ npm install dragonbinder 56 | // With yarn 57 | $ yarn add dragonbinder 58 | ``` 59 | 60 | Or you can use it standalone in the browser with: 61 | `` 62 | 63 | ## Use 64 | 65 | ```javascript 66 | const Dragonbinder = require('dragonbinder'); 67 | 68 | const store = new Dragonbinder({ 69 | state: { 70 | count: 0 71 | }, 72 | mutations: { 73 | increment(state) { 74 | state.count++ 75 | } 76 | } 77 | }); 78 | 79 | store.commit('increment'); 80 | console.log(store.state.count) // -> 1 81 | ``` 82 | 83 | ### State 84 | 85 | **Dragonbinder** use Proxies to create a state as a "single source of truth" which cannot be changed unless you commit a mutation. 86 | This 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. 87 | 88 | If you don't provide an initial state by the `state` property **Dragonbinder** will create one. 89 | 90 | ```javascript 91 | const store = new Dragonbinder({ 92 | state: { 93 | count: 0 94 | }, 95 | mutations: { 96 | addProperty(state, value) { 97 | state.hello = 'world'; 98 | }, 99 | modifyProperty(state) { 100 | state.count++ 101 | }, 102 | removeProperty(state) { 103 | delete state.count; 104 | } 105 | } 106 | }); 107 | 108 | // This will throw errors 109 | store.state.hello = 'world'; 110 | store.state.count++; 111 | delete state.count; 112 | 113 | // This will work as expected 114 | store.commit('addProperty'); 115 | store.commit('modifyProperty'); 116 | store.commit('removeProperty'); 117 | ``` 118 | 119 | Also, if you want to avoid singletons to reuse your initial store definition, you can declare its state as a factory function. 120 | 121 | ```javascript 122 | const myStoreDefinition = { 123 | state(){ 124 | return { 125 | count: 1 126 | } 127 | }, 128 | mutations: { 129 | increment(state, payload) { 130 | state.count = state.count + payload; 131 | } 132 | } 133 | }; 134 | 135 | const store1 = new Dragonbinder(myStoreDefinition); 136 | const store2 = new Dragonbinder(myStoreDefinition); 137 | 138 | store1.commit('increment', 5); 139 | store2.commit('increment', 3); 140 | 141 | console.log(store1.state.count); // -> 6 142 | console.log(store2.state.count); // -> 4 143 | ``` 144 | 145 | ### Getters 146 | 147 | As with Vue, with **Dragonbinder** you can create getters to create computed properties based on the state. 148 | This getters will receive the state as first argument and all other getters as second. 149 | 150 | ```javascript 151 | const store = new Dragonbinder({ 152 | state: { 153 | todos: [ 154 | { 155 | content: 'First', 156 | completed: false 157 | }, 158 | { 159 | content: 'Second', 160 | completed: true 161 | } 162 | ] 163 | }, 164 | getters: { 165 | completed(state){ 166 | return state.todos.filter(item => item.completed); 167 | }, 168 | completedCount(state, getters){ 169 | return getters.completed.length; 170 | } 171 | } 172 | }); 173 | 174 | console.log(store.getters.completed); // -> { content: 'Second', completed: true } 175 | console.log(store.getters.completedCount); // -> 1 176 | ``` 177 | 178 | ### Mutations 179 | 180 | Mutations are the only way to change the state and you must consider the next points when designing mutations. 181 | 182 | - Following the Vuex pattern, mutations must be synchronous. 183 | - Unlike many other libraries you can pass any number of arguments to a mutation. 184 | - 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. 185 | 186 | ```javascript 187 | const store = new Dragonbinder({ 188 | state: { 189 | hello: { 190 | name: 'John Doe' 191 | } 192 | }, 193 | mutations: { 194 | changeNameError(state, payload){ 195 | state.hello.name = payload; 196 | }, 197 | changeNameOk(state, payload){ 198 | state.hello = {...state.hello, name: payload}; 199 | }, 200 | changeNameTo(state, ...args){ 201 | state.hello = {...state.hello, name: args.join(' ')}; 202 | } 203 | } 204 | }); 205 | 206 | // This will throw an assign to read only property error 207 | store.commit('changeNameError', 'Jane Doe'); 208 | 209 | // This will work as expected 210 | store.commit('changeNameOk', 'Jane Doe'); 211 | 212 | // You can pass any number of arguments as payload 213 | store.commit('changeNameTo', 'Jane', 'Doe'); 214 | ``` 215 | 216 | ### Actions 217 | 218 | If you need to handle async functions you must use actions. And actions will always return a promise as result of calling them. 219 | 220 | ```javascript 221 | const store = new Dragonbinder({ 222 | state: { 223 | count: 0 224 | }, 225 | mutations: { 226 | increment(state) { 227 | state.count++ 228 | } 229 | }, 230 | actions: { 231 | increment(state){ 232 | return new Promise((resolve) => { 233 | setTimeout(() => { 234 | store.commit('increment'); 235 | resolve(); 236 | }, 1000); 237 | }) 238 | } 239 | } 240 | }); 241 | 242 | store.dispatch('increment').then(() => console.log(store.state.count)); // -> 1 after one second 243 | ``` 244 | 245 | ### Events 246 | 247 | You can register/unregister callbacks to events. 248 | 249 | ```javascript 250 | const store = new Dragonbinder({ 251 | state: { 252 | count: 0 253 | }, 254 | mutations: { 255 | increment(state) { 256 | state.count++ 257 | } 258 | } 259 | }); 260 | 261 | // Add a named listener 262 | let namedListener = (store, prop, newVal, oldVal) => console.log(`The property ${prop} was changed from ${oldVal} to ${newVal}`); 263 | store.on('set', namedListener); 264 | 265 | // Add an anonymous listener 266 | let removeAnonymousListener = store.on('set', () => console.log('Anonymous listener triggered')); 267 | 268 | // Committing increment will trigger the listener 269 | store.commit('increment'); 270 | // $ The property count was changed from 0 to 1 271 | // $ Anonymous listener triggered 272 | 273 | // Remove a named listener 274 | store.off('set', namedListener); 275 | 276 | // Remove an anonyous listener 277 | removeAnonymousListener(); 278 | 279 | // Committing increment will do nothing as the listeners are already removed 280 | store.commit('increment'); 281 | ``` 282 | 283 | #### Event types 284 | 285 | All events receive the store instance as the first argument. 286 | 287 | | Event name | Its called when | Arguments received by place | 288 | | ---------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | 289 | | addlistener | An event listener is added | Event name \| Listener added | 290 | | removelistener | An event listener is removed | Event name \| Listener removed | 291 | | set | A property of the state is added or modified, also triggered when a module is registered | Property name \| New value \| Old value | 292 | | delete | A property of the state is deleted, also triggered when a module is unregistered | Property name \| Old value | 293 | | beforecommit | Commit method called and before apply the mutation | Mutation name \| (...) Arguments passed to the mutation | 294 | | commit | Commit method called and after apply the mutation | Mutation name \| (...) Arguments passed to the mutation | 295 | | beforedispatch | Dispatch method called and before apply the action | Action name \| (...) Arguments passed to the action | 296 | | dispatch | Dispatch method called and after apply the action | Action name \| (...) Arguments passed to the action | 297 | | getter | A getter is called | Getter name \| Value of the getter | 298 | | plugin | A plugin is added | Plugin added \| (...) Options passed to the plugin | 299 | | registerModule | A module is registered | Namespace registered \| Module definition \| Store created with the definition | 300 | | unregisterModule | A module is unregistered | Namespace unregistered \| Store created with the definition | 301 | 302 | ### Nested modules 303 | 304 | Like Vuex, **Dragonbinder** allows you to divide your store into modules and each module can contain its own store definition including more nested modules. 305 | 306 | ```javascript 307 | const moduleA = { 308 | state: { ... }, 309 | mutations: { ... }, 310 | actions: { ... }, 311 | getters: { ... } 312 | } 313 | 314 | const moduleB = { 315 | state: { ... }, 316 | mutations: { ... }, 317 | actions: { ... }, 318 | getters: { ... } 319 | modules: { 320 | a: moduleA 321 | } 322 | } 323 | 324 | const store = new Dragonbinder({ 325 | modules: { 326 | b: moduleB 327 | } 328 | }); 329 | 330 | console.log(store.state.b) // -> `moduleB`'s state 331 | console.log(store.state['b.a']) // -> `moduleA`'s state 332 | ``` 333 | 334 | Also, after the store is created you can register/unregister modules with the `registerModule` and `unregisterModule` methods. 335 | 336 | Consider that when you unregister a module, only its initial nested modules will be unregistered with it. 337 | 338 | ```javascript 339 | const moduleA = { 340 | state: { ... }, 341 | mutations: { ... }, 342 | actions: { ... }, 343 | getters: { ... } 344 | } 345 | 346 | const moduleB = { 347 | state: { ... }, 348 | mutations: { ... }, 349 | actions: { ... }, 350 | getters: { ... }, 351 | modules: { 352 | a: moduleA 353 | } 354 | } 355 | 356 | const moduleC = { 357 | state: { ... }, 358 | mutations: { ... }, 359 | actions: { ... }, 360 | getters: { ... } 361 | } 362 | 363 | const store = new Dragonbinder(); 364 | store.registerModule('b', moduleB); 365 | store.registerModule('b.c', moduleC); 366 | 367 | console.log(store.state.b) // -> `moduleB`'s state 368 | console.log(store.state['b.a']) // -> `moduleA`'s state 369 | console.log(store.state['b.c']) // -> `moduleC`'s state 370 | 371 | store.unregisterModule('b'); 372 | 373 | console.log(store.state.b) // -> undefined 374 | console.log(store.state['b.a']) // -> undefined 375 | console.log(store.state['b.c']) // -> `moduleC`'s state 376 | ``` 377 | 378 | #### Local and root state 379 | 380 | Each 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. 381 | 382 | The 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. 383 | 384 | Getters will get the root state and root getters as the third and fourth arguments. 385 | 386 | Actions will access the root context by the `rootStore` property of the local context. 387 | 388 | ```javascript 389 | const moduleA = { 390 | state: { 391 | hello: 'world' 392 | }, 393 | mutations: { 394 | sayHello(state, payload){ 395 | state.hello = payload; 396 | } 397 | }, 398 | actions:{ 399 | change(store, payload){ 400 | store.commit('sayHello', payload); 401 | store.rootStore.commit('increment'); 402 | } 403 | }, 404 | getters: { 405 | hello(state, getters, rootState, rootGetters){ 406 | return `You have said hello ${rootState.count} times to ${state.hello}`; 407 | } 408 | } 409 | }; 410 | 411 | const store = new Dragonbinder({ 412 | state: { 413 | count: 0 414 | }, 415 | mutations: { 416 | increment(state){ 417 | state.count++; 418 | } 419 | }, 420 | modules: { 421 | a: moduleA 422 | } 423 | }); 424 | 425 | store.dispatch('a.change', 'John Doe'); 426 | console.log(store.getters['a.hello']); // -> You have said hello 1 times to John Doe 427 | console.log(store.state.count) // -> 1 428 | console.log(store.state.a.hello) // -> John Doe 429 | ``` 430 | 431 | ### Plugin system 432 | 433 | **Dragonbinder** comes with a simple but powerfull plugin system. 434 | You can extend its core functionality or change it completely by making use of plugins. 435 | 436 | #### Using plugins 437 | 438 | ```javascript 439 | let store = new Dragonbinder(); 440 | store.use(myPlugin, ...options); 441 | ``` 442 | 443 | #### Developing plugins 444 | 445 | A **Dragonbinder** plugin is a module that exports a single function that will be called 446 | with the store instance as first argument and optionally with the passed options if any. 447 | 448 | ```javascript 449 | const Dragonbinder = require('dragonbinder'); 450 | const myPlugin = (store, ...options) => { 451 | 452 | Dragonbinder.myGlobalMethod = function() { 453 | // Awesome code here 454 | }; 455 | 456 | Dragonbinder.fn.myPrototypeMethod = function() { 457 | // Awesome code here 458 | }; 459 | 460 | store.myLocalMethod = function() { 461 | // Awesome code here 462 | }; 463 | }; 464 | ``` 465 | 466 | ## API 467 | 468 | Check the docs at: 469 | 470 | ## Contributing 471 | 472 | Check the contributing guide at: 473 | 474 | ## Development, Build and Tests 475 | 476 | - Use `yarn dev` to watch and compile the library on every change to it. 477 | - Use `yarn build` to build the library. 478 | - Use `yarn test` to run tests only once. 479 | - Use `yarn dev:test` to run the tests watching changes to library and tests. 480 | - Use `yarn dev:test:nyc` to run the tests watching changes and get the test coverage at last. 481 | - Use `yarn docs` to build the documentation. 482 | - Use `yarn docs:watch` to watch and rebuild the documentation on every change to the library or the md files. 483 | - Use `yarn docs:serve` to see the generated documentation locally. 484 | 485 | ## Legal 486 | 487 | Author: [Masquerade Circus](http://masquerade-circus.net). License [Apache-2.0](https://opensource.org/licenses/Apache-2.0) 488 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable max-len */ 3 | const esbuild = require("esbuild"); 4 | const terser = require("terser"); 5 | const fs = require("fs"); 6 | const zlib = require("zlib"); 7 | 8 | function convertToUMD(text, globalName) { 9 | // HACK: convert to UMD - only supports cjs and global var 10 | const varName = "__EXPORTS__"; 11 | let code = text; 12 | 13 | code = code.replace(/export\s*\{([^{}]+)\}/, (_, inner) => { 14 | const defaultExport = inner.match(/^(\w+) as default$/); 15 | return defaultExport != null 16 | ? `var ${varName}=${defaultExport[1]}` 17 | : `var ${varName}={${inner.replace(/(\w+) as (\w+)/g, "$2:$1")}}`; 18 | }); 19 | 20 | code = code.replace(/export\s*default\s*(\w+)/, (_, name) => { 21 | return `var ${varName}=${name}`; 22 | }); 23 | 24 | code = code.replace(/module.exports\s*=\s*(\w+)/, (_, name) => { 25 | return `var ${varName}=${name}`; 26 | }); 27 | 28 | code = `(()=>{${code};typeof module!=='undefined'?module.exports=${varName}:self.${globalName}=${varName}})()`; 29 | return code; 30 | } 31 | 32 | async function build({ 33 | globalName, 34 | entryPoint, 35 | outfileName, 36 | clean = false, 37 | minify = true, 38 | minifyAs = "esm", 39 | external = [] 40 | }) { 41 | try { 42 | let rootdir = entryPoint.split("/").slice(0, -1).join("/"); 43 | let outdir = outfileName.split("/").slice(0, -1).join("/"); 44 | let outfile = outfileName.split("/").pop(); 45 | 46 | if (entryPoint.endsWith(".ts")) { 47 | const tsc = require("tsc-prog"); 48 | let tscProgOptions2 = { 49 | basePath: __dirname, // always required, used for relative paths 50 | configFilePath: "tsconfig.json", // config to inherit from (optional) 51 | files: [entryPoint], 52 | pretty: true, 53 | copyOtherToOutDir: false, 54 | clean: clean ? [outdir] : [], 55 | skipLibCheck: true, 56 | compilerOptions: { 57 | rootDir: rootdir, 58 | declaration: true, 59 | outDir: outdir, 60 | emitDeclarationOnly: true 61 | }, 62 | bundleDeclaration: { 63 | entryPoint: "index.d.ts" // relative to the OUTPUT directory ('dist' here) 64 | } 65 | }; 66 | 67 | tsc.build(tscProgOptions2); 68 | } 69 | 70 | let cjs = esbuild.buildSync({ 71 | entryPoints: [entryPoint], 72 | bundle: true, 73 | sourcemap: "external", 74 | write: false, 75 | minify: false, 76 | outdir: outdir, 77 | target: "esnext", 78 | loader: { ".js": "jsx", ".ts": "tsx", ".mjs": "jsx" }, 79 | format: "cjs", 80 | metafile: true, 81 | external 82 | }); 83 | 84 | let esm = esbuild.buildSync({ 85 | entryPoints: [entryPoint], 86 | bundle: true, 87 | sourcemap: "external", 88 | write: false, 89 | minify: false, 90 | outdir: outdir, 91 | target: "esnext", 92 | loader: { ".js": "jsx", ".ts": "tsx", ".mjs": "jsx" }, 93 | format: "esm", 94 | metafile: true, 95 | external 96 | }); 97 | 98 | let esmContent = esm.outputFiles[1].text; 99 | 100 | // HACK: simulate __dirname and __filename for esm 101 | if ( 102 | esmContent.indexOf("__dirname") !== -1 || 103 | esmContent.indexOf("__filename") !== -1 104 | ) { 105 | esmContent = 106 | `import { fileURLToPath } from 'url';\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n` + 107 | esmContent; 108 | if (esmContent.indexOf("import path from") === -1) { 109 | esmContent = `import path from 'path';\n` + esmContent; 110 | } 111 | } 112 | 113 | fs.writeFileSync(`${outfileName}.mjs`, esmContent); 114 | fs.writeFileSync(`${outfileName}.js`, cjs.outputFiles[1].text); 115 | 116 | let text = await esbuild.analyzeMetafile(esm.metafile, { verbose: true }); 117 | console.log(text); 118 | 119 | let result2; 120 | if (minify) { 121 | let codeToMinify = 122 | minifyAs === "esm" ? esm : (minifyAs = "cjs" ? cjs : null); 123 | if (codeToMinify) { 124 | let code = convertToUMD(codeToMinify.outputFiles[1].text, globalName); 125 | result2 = await terser.minify(code, { 126 | sourceMap: { 127 | content: codeToMinify.outputFiles[0].text.toString() 128 | }, 129 | compress: { 130 | booleans_as_integers: false 131 | }, 132 | output: { 133 | wrap_func_args: false 134 | }, 135 | ecma: 2022 136 | }); 137 | 138 | let mapBase64 = Buffer.from(result2.map.toString()).toString("base64"); 139 | let map = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${mapBase64}`; 140 | fs.writeFileSync( 141 | `${outfileName}.min.js`, 142 | result2.code + `//# sourceMappingURL=${outfile}.min.js.map` 143 | ); 144 | fs.writeFileSync(`${outfileName}.min.js.map`, map); 145 | } 146 | } 147 | 148 | function formatBytesToKiloBytes(bytes) { 149 | return (bytes / 1024).toFixed(2) + "kb"; 150 | } 151 | 152 | let header = `/*** ${entryPoint} ***/`; 153 | console.log(header); 154 | console.log("Esm", formatBytesToKiloBytes(esm.outputFiles[1].text.length)); 155 | if (minify) { 156 | console.log("Minified:", formatBytesToKiloBytes(result2.code.length)); 157 | // Get the size using gzip compression 158 | const gzip = zlib.gzipSync(result2.code); 159 | console.log("Gzip:", formatBytesToKiloBytes(gzip.length)); 160 | // Get the size using brotli algorithm 161 | const brotli = zlib.brotliCompressSync(result2.code); 162 | console.log("Brotli:", formatBytesToKiloBytes(brotli.length)); 163 | } 164 | console.log(`/${Array(header.length).fill("*").join("")}/`); 165 | } catch (e) { 166 | console.error(e); 167 | } 168 | } 169 | 170 | build({ 171 | globalName: "Dragonbinder", 172 | entryPoint: "./lib/index.js", 173 | outfileName: "./dist/dragonbinder", 174 | clean: true, 175 | minify: true, 176 | minifyAs: "cjs" 177 | }); 178 | -------------------------------------------------------------------------------- /dist/dragonbinder.js: -------------------------------------------------------------------------------- 1 | // lib/index.js 2 | function deepFreeze(obj) { 3 | if (typeof obj === "object" && obj !== null && !Object.isFrozen(obj)) { 4 | if (Array.isArray(obj)) { 5 | for (let i = 0, l = obj.length; i < l; i++) { 6 | deepFreeze(obj[i]); 7 | } 8 | } else { 9 | let props = Reflect.ownKeys(obj); 10 | for (let i = 0, l = props.length; i < l; i++) { 11 | deepFreeze(obj[props[i]]); 12 | } 13 | } 14 | Object.freeze(obj); 15 | } 16 | return obj; 17 | } 18 | function Dragonbinder({ 19 | state, 20 | mutations, 21 | actions, 22 | getters, 23 | modules, 24 | shouldFreeze = true, 25 | namespace, 26 | rootStore 27 | } = {}) { 28 | let localState = typeof state === "function" ? state() : state; 29 | this.state = new Proxy(localState || {}, { 30 | get: (state2, prop) => { 31 | if ((this.rootStore || this).init.modules[prop]) { 32 | return state2[prop]; 33 | } 34 | if (shouldFreeze) { 35 | return deepFreeze(state2[prop]); 36 | } 37 | return state2[prop]; 38 | }, 39 | set: (state2, prop, value) => { 40 | this.isUnfrozen(); 41 | let old = state2[prop]; 42 | state2[prop] = value; 43 | if (this.namespace) { 44 | prop = `${this.namespace}.${prop}`; 45 | } 46 | (this.rootStore || this).trigger("set", prop, value, old); 47 | return true; 48 | }, 49 | deleteProperty: (state2, prop) => { 50 | this.isUnfrozen(); 51 | let old = state2[prop]; 52 | delete state2[prop]; 53 | if (this.namespace) { 54 | prop = `${this.namespace}.${prop}`; 55 | } 56 | (this.rootStore || this).trigger("delete", prop, old); 57 | return true; 58 | } 59 | }); 60 | Object.defineProperty(this, "init", { 61 | value: { 62 | frozen: true, 63 | plugins: [], 64 | modules: {}, 65 | childModuleNamespaces: Object.keys(modules || {}), 66 | listeners: { 67 | set: [], 68 | delete: [], 69 | beforecommit: [], 70 | commit: [], 71 | beforedispatch: [], 72 | dispatch: [], 73 | getter: [], 74 | addlistener: [], 75 | removelistener: [], 76 | plugin: [], 77 | registerModule: [], 78 | unregisterModule: [] 79 | }, 80 | getters: getters || {}, 81 | mutations: mutations || {}, 82 | actions: actions || {} 83 | } 84 | }); 85 | this.getters = new Proxy(getters || {}, { 86 | get: (getters2, getter) => { 87 | try { 88 | let { store, key } = this.getStore(this, getter); 89 | if (store instanceof Dragonbinder && store.init.getters[key]) { 90 | let value = store.init.getters[key](store.state, store.getters, this.state, this.getters); 91 | if (this.namespace) { 92 | getter = `${this.namespace}.${getter}`; 93 | } 94 | (this.rootStore || this).trigger("getter", getter, value); 95 | return value; 96 | } 97 | } catch (error) { 98 | return; 99 | } 100 | } 101 | }); 102 | Object.defineProperty(this, "rootStore", { 103 | value: rootStore || null, 104 | enumerable: true 105 | }); 106 | Object.defineProperty(this, "namespace", { 107 | value: namespace || null, 108 | enumerable: true 109 | }); 110 | if (modules) { 111 | Object.keys(modules).forEach((namespace2) => { 112 | let n = this.namespace ? `${this.namespace}.${namespace2}` : namespace2; 113 | (this.rootStore || this).registerModule(n, modules[namespace2]); 114 | }); 115 | } 116 | } 117 | Dragonbinder.prototype = Dragonbinder.fn = { 118 | keyExists(objectname, object, key) { 119 | if (!object[key]) { 120 | throw new Error(`The ${objectname} "${key}" does not exists.`); 121 | } 122 | }, 123 | isFunction(type, callback) { 124 | if (typeof callback !== "function") { 125 | throw new Error(`You need to provide a valid function as ${type}.`); 126 | } 127 | }, 128 | getStore(store, namespace) { 129 | let key = namespace; 130 | if (key.indexOf(".") > -1) { 131 | let parts = key.split("."); 132 | key = parts.pop(); 133 | let moduleName = parts.join("."); 134 | this.keyExists("module", store.init.modules, moduleName); 135 | store = store.init.modules[moduleName]; 136 | } 137 | return { 138 | store, 139 | key 140 | }; 141 | }, 142 | isUnfrozen() { 143 | if (this.init.frozen) { 144 | throw new Error("You need to commit a mutation to change the state"); 145 | } 146 | }, 147 | commit(mutation, ...args) { 148 | let { store, key } = this.getStore(this, mutation); 149 | this.keyExists("mutation", store.init.mutations, key); 150 | store.init.frozen = false; 151 | this.trigger("beforecommit", mutation, ...args); 152 | store.init.mutations[key](store.state, ...args); 153 | this.trigger("commit", mutation, ...args); 154 | store.init.frozen = true; 155 | }, 156 | dispatch(action, ...args) { 157 | let { store, key } = this.getStore(this, action); 158 | this.keyExists("action", store.init.actions, key); 159 | store.init.frozen = false; 160 | this.trigger("beforedispatch", action, ...args); 161 | return Promise.resolve(store.init.actions[key](store, ...args)).then((result) => { 162 | this.trigger("dispatch", action, ...args); 163 | return result; 164 | }); 165 | }, 166 | trigger(event, ...args) { 167 | this.init.listeners[event].forEach((callback) => callback(this, ...args)); 168 | }, 169 | on(event, listener) { 170 | this.isFunction("listener", listener); 171 | this.keyExists("event", this.init.listeners, event); 172 | if (this.init.listeners[event].indexOf(listener) === -1) { 173 | this.init.listeners[event].push(listener); 174 | this.trigger("addlistener", event, listener); 175 | } 176 | return () => this.off(event, listener); 177 | }, 178 | off(event, listener) { 179 | this.isFunction("listener", listener); 180 | this.keyExists("event", this.init.listeners, event); 181 | let index = this.init.listeners[event].indexOf(listener); 182 | if (index > -1) { 183 | this.init.listeners[event].splice(index, 1); 184 | this.trigger("removelistener", event, listener); 185 | } 186 | }, 187 | use(plugin, ...options) { 188 | this.isFunction("plugin", plugin); 189 | if (this.init.plugins.indexOf(plugin) === -1) { 190 | plugin(this, ...options); 191 | this.init.plugins.push(plugin); 192 | this.trigger("plugin", plugin, ...options); 193 | } 194 | }, 195 | registerModule(namespace, module2) { 196 | let rootStore = this; 197 | if (rootStore.init.modules[namespace]) { 198 | throw new Error(`A module with the namespace "${namespace}" is already registered.`); 199 | } 200 | let newStore = new Dragonbinder(Object.assign({ rootStore, namespace }, module2)); 201 | rootStore.init.frozen = false; 202 | rootStore.init.modules[namespace] = newStore; 203 | rootStore.state[namespace] = newStore.state; 204 | rootStore.init.frozen = true; 205 | rootStore.trigger("registerModule", namespace, module2, newStore); 206 | }, 207 | unregisterModule(namespace) { 208 | let rootStore = this; 209 | let store = rootStore.init.modules[namespace]; 210 | if (store) { 211 | store.init.childModuleNamespaces.forEach((n) => rootStore.unregisterModule(`${namespace}.${n}`)); 212 | rootStore.init.frozen = false; 213 | delete rootStore.init.modules[namespace]; 214 | delete rootStore.state[namespace]; 215 | rootStore.init.frozen = true; 216 | rootStore.trigger("unregisterModule", namespace, store); 217 | } 218 | } 219 | }; 220 | Dragonbinder.fn = Dragonbinder.prototype; 221 | if (typeof exports === "object") { 222 | module.exports = Dragonbinder; 223 | } else { 224 | (typeof window === "undefined" ? global : window).Dragonbinder = Dragonbinder; 225 | } 226 | -------------------------------------------------------------------------------- /dist/dragonbinder.min.js: -------------------------------------------------------------------------------- 1 | (()=>{function e(t){if("object"==typeof t&&null!==t&&!Object.isFrozen(t)){if(Array.isArray(t))for(let i=0,s=t.length;i(this.rootStore||this).init.modules[i]?t[i]:l?e(t[i]):t[i],set:(e,t,i)=>{this.isUnfrozen();let s=e[t];return e[t]=i,this.namespace&&(t=`${this.namespace}.${t}`),(this.rootStore||this).trigger("set",t,i,s),!0},deleteProperty:(e,t)=>{this.isUnfrozen();let i=e[t];return delete e[t],this.namespace&&(t=`${this.namespace}.${t}`),(this.rootStore||this).trigger("delete",t,i),!0}}),Object.defineProperty(this,"init",{value:{frozen:!0,plugins:[],modules:{},childModuleNamespaces:Object.keys(o||{}),listeners:{set:[],delete:[],beforecommit:[],commit:[],beforedispatch:[],dispatch:[],getter:[],addlistener:[],removelistener:[],plugin:[],registerModule:[],unregisterModule:[]},getters:n||{},mutations:s||{},actions:r||{}}}),this.getters=new Proxy(n||{},{get:(e,i)=>{try{let{store:e,key:s}=this.getStore(this,i);if(e instanceof t&&e.init.getters[s]){let t=e.init.getters[s](e.state,e.getters,this.state,this.getters);return this.namespace&&(i=`${this.namespace}.${i}`),(this.rootStore||this).trigger("getter",i,t),t}}catch(e){return}}}),Object.defineProperty(this,"rootStore",{value:a||null,enumerable:!0}),Object.defineProperty(this,"namespace",{value:h||null,enumerable:!0}),o&&Object.keys(o).forEach(e=>{let t=this.namespace?`${this.namespace}.${e}`:e;(this.rootStore||this).registerModule(t,o[e])})}if(t.prototype=t.fn={keyExists(e,t,i){if(!t[i])throw new Error(`The ${e} "${i}" does not exists.`)},isFunction(e,t){if("function"!=typeof t)throw new Error(`You need to provide a valid function as ${e}.`)},getStore(e,t){let i=t;if(i.indexOf(".")>-1){let t=i.split(".");i=t.pop();let s=t.join(".");this.keyExists("module",e.init.modules,s),e=e.init.modules[s]}return{store:e,key:i}},isUnfrozen(){if(this.init.frozen)throw new Error("You need to commit a mutation to change the state")},commit(e,...t){let{store:i,key:s}=this.getStore(this,e);this.keyExists("mutation",i.init.mutations,s),i.init.frozen=!1,this.trigger("beforecommit",e,...t),i.init.mutations[s](i.state,...t),this.trigger("commit",e,...t),i.init.frozen=!0},dispatch(e,...t){let{store:i,key:s}=this.getStore(this,e);return this.keyExists("action",i.init.actions,s),i.init.frozen=!1,this.trigger("beforedispatch",e,...t),Promise.resolve(i.init.actions[s](i,...t)).then(i=>(this.trigger("dispatch",e,...t),i))},trigger(e,...t){this.init.listeners[e].forEach(e=>e(this,...t))},on(e,t){return this.isFunction("listener",t),this.keyExists("event",this.init.listeners,e),-1===this.init.listeners[e].indexOf(t)&&(this.init.listeners[e].push(t),this.trigger("addlistener",e,t)),()=>this.off(e,t)},off(e,t){this.isFunction("listener",t),this.keyExists("event",this.init.listeners,e);let i=this.init.listeners[e].indexOf(t);i>-1&&(this.init.listeners[e].splice(i,1),this.trigger("removelistener",e,t))},use(e,...t){this.isFunction("plugin",e),-1===this.init.plugins.indexOf(e)&&(e(this,...t),this.init.plugins.push(e),this.trigger("plugin",e,...t))},registerModule(e,i){let s=this;if(s.init.modules[e])throw new Error(`A module with the namespace "${e}" is already registered.`);let r=new t(Object.assign({rootStore:s,namespace:e},i));s.init.frozen=!1,s.init.modules[e]=r,s.state[e]=r.state,s.init.frozen=!0,s.trigger("registerModule",e,i,r)},unregisterModule(e){let t=this,i=t.init.modules[e];i&&(i.init.childModuleNamespaces.forEach(i=>t.unregisterModule(`${e}.${i}`)),t.init.frozen=!1,delete t.init.modules[e],delete t.state[e],t.init.frozen=!0,t.trigger("unregisterModule",e,i))}},t.fn=t.prototype,"object"==typeof exports)var i=t;else("undefined"==typeof window?global:window).Dragonbinder=t;"undefined"!=typeof module?module.exports=i:self.Dragonbinder=i})();//# sourceMappingURL=dragonbinder.min.js.map -------------------------------------------------------------------------------- /dist/dragonbinder.min.js.map: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJkZWVwRnJlZXplIiwib2JqIiwiT2JqZWN0IiwiaXNGcm96ZW4iLCJBcnJheSIsImlzQXJyYXkiLCJpIiwibCIsImxlbmd0aCIsInByb3BzIiwiUmVmbGVjdCIsIm93bktleXMiLCJmcmVlemUiLCJEcmFnb25iaW5kZXIiLCJzdGF0ZSIsIm11dGF0aW9ucyIsImFjdGlvbnMiLCJnZXR0ZXJzIiwibW9kdWxlcyIsInNob3VsZEZyZWV6ZSIsIm5hbWVzcGFjZSIsInJvb3RTdG9yZSIsImxvY2FsU3RhdGUiLCJ0aGlzIiwiUHJveHkiLCJnZXQiLCJzdGF0ZTIiLCJwcm9wIiwiaW5pdCIsInNldCIsInZhbHVlIiwiaXNVbmZyb3plbiIsIm9sZCIsInRyaWdnZXIiLCJkZWxldGVQcm9wZXJ0eSIsImRlZmluZVByb3BlcnR5IiwiZnJvemVuIiwicGx1Z2lucyIsImNoaWxkTW9kdWxlTmFtZXNwYWNlcyIsImtleXMiLCJsaXN0ZW5lcnMiLCJkZWxldGUiLCJiZWZvcmVjb21taXQiLCJjb21taXQiLCJiZWZvcmVkaXNwYXRjaCIsImRpc3BhdGNoIiwiZ2V0dGVyIiwiYWRkbGlzdGVuZXIiLCJyZW1vdmVsaXN0ZW5lciIsInBsdWdpbiIsInJlZ2lzdGVyTW9kdWxlIiwidW5yZWdpc3Rlck1vZHVsZSIsImdldHRlcnMyIiwic3RvcmUiLCJrZXkiLCJnZXRTdG9yZSIsImVycm9yIiwiZW51bWVyYWJsZSIsImZvckVhY2giLCJuYW1lc3BhY2UyIiwibiIsInByb3RvdHlwZSIsImZuIiwia2V5RXhpc3RzIiwib2JqZWN0bmFtZSIsIm9iamVjdCIsIkVycm9yIiwiaXNGdW5jdGlvbiIsInR5cGUiLCJjYWxsYmFjayIsImluZGV4T2YiLCJwYXJ0cyIsInNwbGl0IiwicG9wIiwibW9kdWxlTmFtZSIsImpvaW4iLCJtdXRhdGlvbiIsImFyZ3MiLCJhY3Rpb24iLCJQcm9taXNlIiwicmVzb2x2ZSIsInRoZW4iLCJyZXN1bHQiLCJldmVudCIsIm9uIiwibGlzdGVuZXIiLCJwdXNoIiwib2ZmIiwiaW5kZXgiLCJzcGxpY2UiLCJ1c2UiLCJvcHRpb25zIiwibW9kdWxlMiIsIm5ld1N0b3JlIiwiYXNzaWduIiwiZXhwb3J0cyIsIl9fRVhQT1JUU19fIiwid2luZG93IiwiZ2xvYmFsIl0sInNvdXJjZXMiOlsiLi4vbGliL2luZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhpcyBtZXRob2QgaXMgdXNlZCB0byBkZWVwIGZyZWV6ZSBhbiBvYmplY3RcbiAqIEBwYXJhbSB7T2JqZWN0fSBvYmogVGhlIG9iamVjdCB0byBmcmVlemVcbiAqIEByZXR1cm5zIHtPYmplY3R9IE9iamVjdCBmcm96ZW5cbiAqL1xuZnVuY3Rpb24gZGVlcEZyZWV6ZShvYmopIHtcbiAgaWYgKHR5cGVvZiBvYmogPT09IFwib2JqZWN0XCIgJiYgb2JqICE9PSBudWxsICYmICFPYmplY3QuaXNGcm96ZW4ob2JqKSkge1xuICAgIGlmIChBcnJheS5pc0FycmF5KG9iaikpIHtcbiAgICAgIGZvciAobGV0IGkgPSAwLCBsID0gb2JqLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICBkZWVwRnJlZXplKG9ialtpXSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxldCBwcm9wcyA9IFJlZmxlY3Qub3duS2V5cyhvYmopO1xuICAgICAgZm9yIChsZXQgaSA9IDAsIGwgPSBwcm9wcy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgZGVlcEZyZWV6ZShvYmpbcHJvcHNbaV1dKTtcbiAgICAgIH1cbiAgICB9XG4gICAgT2JqZWN0LmZyZWV6ZShvYmopO1xuICB9XG5cbiAgcmV0dXJuIG9iajtcbn1cblxuLyoqXG4gKiBUaGlzIGlzIHRoZSBTdG9yZSBlbnRpdHlcbiAqXG4gKiBAY29uc3RydWN0b3JcbiAqIEBwYXJhbSB7T2JqZWN0fSBkYXRhIFRoZSBpbml0aWFsIG9wdGlvbnMgdG8gY3JlYXRlIHRoZSBzdG9yZVxuICogQHBhcmFtIHtPYmplY3R9IFtkYXRhLnN0YXRlXSBPcHRpb25hbCBpbml0aWFsIHN0YXRlIGZvciB0aGUgc3RvcmVcbiAqIEBwYXJhbSB7T2JqZWN0fSBbZGF0YS5tdXRhdGlvbnNdIE9wdGlvbmFsIG11dGF0aW9uIG1ldGhvZHMgdG8gdXBkYXRlIHRoZSBzdGF0ZVxuICogQHBhcmFtIHtPYmplY3R9IFtkYXRhLmFjdGlvbnNdIE9wdGlvbmFsIGFjdGlvbnMgdG8gaGFuZGxlIGFzeW5jIG1ldGhvZHMgYW5kIHVwZGF0ZSB0aGUgc3RhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBbZGF0YS5nZXR0ZXJzXSBPcHRpb25hbCBnZXR0ZXJzIHRvIHJlYWQgZnJvbSB0aGUgc3RhdGVcbiAqIEBwYXJhbSB7T2JqZWN0fSBbZGF0YS5tb2R1bGVzXSBPcHRpb25hbCBtb2R1bGVzIHRvIHJlZ2lzdGVyIHRvIHRoaXMgc3RvcmVcbiAqIEBwYXJhbSB7Qm9vbGVhbn0gW2RhdGEuc2hvdWxkRnJlZXplPXRydWVdIFdoZXRoZXIgdG8gZnJlZXplIHRoZSBzdGF0ZVxuICogQHJldHVybnMge09iamVjdH0gbmV3IHN0b3JlIG9iamVjdFxuICovXG5mdW5jdGlvbiBEcmFnb25iaW5kZXIoe1xuICBzdGF0ZSxcbiAgbXV0YXRpb25zLFxuICBhY3Rpb25zLFxuICBnZXR0ZXJzLFxuICBtb2R1bGVzLFxuICBzaG91bGRGcmVlemUgPSB0cnVlLFxuICBuYW1lc3BhY2UsXG4gIHJvb3RTdG9yZVxufSA9IHt9KSB7XG4gIC8vIEluaXRpYWxpemUgdGhlIGxvY2FsU3RhdGUgZm9yIHRoaXMgc3RvcmVcbiAgbGV0IGxvY2FsU3RhdGUgPSB0eXBlb2Ygc3RhdGUgPT09IFwiZnVuY3Rpb25cIiA/IHN0YXRlKCkgOiBzdGF0ZTtcblxuICAvKipcbiAgICogV2UgY3JlYXRlIGEgcHJveHkgZm9yIHRoZSBzdGF0ZVxuICAgKiBAdHlwZSB7T2JqZWN0fVxuICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuc3RhdGVcbiAgICovXG4gIHRoaXMuc3RhdGUgPSBuZXcgUHJveHkobG9jYWxTdGF0ZSB8fCB7fSwge1xuICAgIC8qKlxuICAgICAqIEV2ZXJ5IHRpbWUgd2UgdHJ5IHRvIGFjY2VzcyBhIHByb3BlcnR5IGZyb20gdGhlIHN0YXRlIHdlIHRyeSB0byBkZWVwIGZyZWV6ZSB0aGUgcHJvcGVydHlcbiAgICAgKiB0byBwcmV2ZW50IGRpcmVjdCBtb2RpZmljYXRpb25zIHRvIHRoZSBzdGF0ZVxuICAgICAqXG4gICAgICogQHR5cGUge09iamVjdH1cbiAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuc3RhdGUuZ2V0XG4gICAgICogQHBhcmFtIHtPYmplY3R9IHN0YXRlIFRoZSBzdGF0ZSBvYmplY3RcbiAgICAgKiBAcGFyYW0ge1N0cmluZ30gcHJvcCBUaGUgcHJvcGVydHkgdG8gcmVhZFxuICAgICAqIEByZXR1cm5zIHtBbnl9IFRoZSBwcm9wZXJ0eSB2YWx1ZVxuICAgICAqL1xuICAgIGdldDogKHN0YXRlLCBwcm9wKSA9PiB7XG4gICAgICBpZiAoKHRoaXMucm9vdFN0b3JlIHx8IHRoaXMpLmluaXQubW9kdWxlc1twcm9wXSkge1xuICAgICAgICByZXR1cm4gc3RhdGVbcHJvcF07XG4gICAgICB9XG4gICAgICBpZiAoc2hvdWxkRnJlZXplKSB7XG4gICAgICAgIHJldHVybiBkZWVwRnJlZXplKHN0YXRlW3Byb3BdKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHN0YXRlW3Byb3BdO1xuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBJZiB0aGUgdXNlciB0cmllcyB0byBzZXQgZGlyZWN0bHkgaXQgd2lsbCB0aHJvdyBhbiBlcnJvciwgb25seSBpZiB3ZSBoYXZlIHVuZnJvemVuIHRoZSBzdGF0ZSB2aWEgY29tbWl0XG4gICAgICogdGhpcyB3aWxsIHByb2NlZWQgdG8gc2V0IHRoZSB2YWx1ZVxuICAgICAqXG4gICAgICogQHR5cGUge09iamVjdH1cbiAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuc3RhdGUuc2V0XG4gICAgICogQHBhcmFtIHtPYmplY3R9IHN0YXRlIFRoZSBzdGF0ZSBvYmplY3RcbiAgICAgKiBAcGFyYW0ge1N0cmluZ30gcHJvcCBUaGUgcHJvcGVydHkgdG8gc2V0XG4gICAgICogQHBhcmFtIHtBbnl9IHZhbHVlIFRoZSBwcm9wZXJ0eSB2YWx1ZVxuICAgICAqIEByZXR1cm5zIHtCb29sZWFufSBBbHdheXMgcmV0dXJucyB0cnVlXG4gICAgICovXG4gICAgc2V0OiAoc3RhdGUsIHByb3AsIHZhbHVlKSA9PiB7XG4gICAgICB0aGlzLmlzVW5mcm96ZW4oKTtcbiAgICAgIGxldCBvbGQgPSBzdGF0ZVtwcm9wXTtcbiAgICAgIHN0YXRlW3Byb3BdID0gdmFsdWU7XG4gICAgICBpZiAodGhpcy5uYW1lc3BhY2UpIHtcbiAgICAgICAgcHJvcCA9IGAke3RoaXMubmFtZXNwYWNlfS4ke3Byb3B9YDtcbiAgICAgIH1cbiAgICAgICh0aGlzLnJvb3RTdG9yZSB8fCB0aGlzKS50cmlnZ2VyKFwic2V0XCIsIHByb3AsIHZhbHVlLCBvbGQpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIElmIHRoZSB1c2VyIHRyaWVzIHRvIGRlbGV0ZSBkaXJlY3RseSBpdCB3aWxsIHRocm93IGFuIGVycm9yLCBvbmx5IGlmIHdlIGhhdmUgdW5mcm96ZW4gdGhlIHN0YXRlIHZpYSBjb21taXRcbiAgICAgKiB0aGlzIHdpbGwgcHJvY2VlZCB0byBkZWxldGUgdGhlIHByb3BlcnR5XG4gICAgICpcbiAgICAgKiBAdHlwZSB7T2JqZWN0fVxuICAgICAqIEBuYW1lIERyYWdvbmJpbmRlci5zdGF0ZS5kZWxldGVQcm9wZXJ0eVxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBzdGF0ZSBUaGUgc3RhdGUgb2JqZWN0XG4gICAgICogQHBhcmFtIHtTdHJpbmd9IHByb3AgVGhlIHByb3BlcnR5IHRvIGRlbGV0ZVxuICAgICAqIEByZXR1cm5zIHtCb29sZWFufSBBbHdheXMgcmV0dXJucyB0cnVlXG4gICAgICovXG4gICAgZGVsZXRlUHJvcGVydHk6IChzdGF0ZSwgcHJvcCkgPT4ge1xuICAgICAgdGhpcy5pc1VuZnJvemVuKCk7XG4gICAgICBsZXQgb2xkID0gc3RhdGVbcHJvcF07XG4gICAgICBkZWxldGUgc3RhdGVbcHJvcF07XG4gICAgICBpZiAodGhpcy5uYW1lc3BhY2UpIHtcbiAgICAgICAgcHJvcCA9IGAke3RoaXMubmFtZXNwYWNlfS4ke3Byb3B9YDtcbiAgICAgIH1cbiAgICAgICh0aGlzLnJvb3RTdG9yZSB8fCB0aGlzKS50cmlnZ2VyKFwiZGVsZXRlXCIsIHByb3AsIG9sZCk7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gIH0pO1xuXG4gIC8qKlxuICAgKiBXZSB3aWxsIGRlZmluZSBhIGhpZGRlbiBwcm9wZXJ0eSB0byBzdG9yZSB0aGUgaW5pdCBkYXRhXG4gICAqXG4gICAqIEB0eXBlIHtPYmplY3R9XG4gICAqIEBuYW1lIERyYWdvbmJpbmRlci5pbml0XG4gICAqL1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgXCJpbml0XCIsIHtcbiAgICB2YWx1ZToge1xuICAgICAgLyoqXG4gICAgICAgKiBUaGlzIHByb3BlcnR5IHdpbGwgc3RvcmUgdGhlIGZyb3plbiBzdGF0ZSBvZiB0aGUgc3RvcmVcbiAgICAgICAqXG4gICAgICAgKiBAdHlwZSB7Qm9vbGVhbn1cbiAgICAgICAqIEBuYW1lIERyYWdvbmJpbmRlci5pbml0LmZyb3plblxuICAgICAgICovXG4gICAgICBmcm96ZW46IHRydWUsXG5cbiAgICAgIC8qKlxuICAgICAgICogVGhpcyBwcm9wZXJ0eSB3aWxsIHN0b3JlIHRoZSBwbHVnaW5zXG4gICAgICAgKiBAdHlwZSB7QXJyYXl9XG4gICAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuaW5pdC5wbHVnaW5zXG4gICAgICAgKi9cbiAgICAgIHBsdWdpbnM6IFtdLFxuXG4gICAgICAvKipcbiAgICAgICAqIFRoaXMgcHJvcGVydHkgd2lsbCBzdG9yZSB0aGUgcmVnaXN0ZXJlZCBtb2R1bGVzXG4gICAgICAgKiBAdHlwZSB7T2JqZWN0fVxuICAgICAgICogQG5hbWUgRHJhZ29uYmluZGVyLmluaXQubW9kdWxlc1xuICAgICAgICovXG4gICAgICBtb2R1bGVzOiB7fSxcblxuICAgICAgLyoqXG4gICAgICAgKiBUaGlzIHdpbGwgaGF2ZSB0aGUgaW5pdGlhbCBjaGlsZCBtb2R1bGUgbmFtZXNwYWNlIGRlZmluaXRpb25zXG4gICAgICAgKiBAdHlwZSB7QXJyYXl9XG4gICAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuaW5pdC5jaGlsZE1vZHVsZU5hbWVzcGFjZXNcbiAgICAgICAqL1xuICAgICAgY2hpbGRNb2R1bGVOYW1lc3BhY2VzOiBPYmplY3Qua2V5cyhtb2R1bGVzIHx8IHt9KSxcblxuICAgICAgLyoqXG4gICAgICAgKiBUaGlzIHByb3BlcnR5IHdpbGwgc3RvcmUgdGhlIGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICogQHR5cGUge09iamVjdH1cbiAgICAgICAqIEBuYW1lIERyYWdvbmJpbmRlci5pbml0Lmxpc3RlbmVyc1xuICAgICAgICogQHByb3BlcnR5IHtBcnJheX0gbGlzdGVuZXJzLnNldCBUaGlzIHdpbGwgc3RvcmUgdGhlIHNldCBldmVudCBsaXN0ZW5lcnNcbiAgICAgICAqIEBwcm9wZXJ0eSB7QXJyYXl9IGxpc3RlbmVycy5kZWxldGUgVGhpcyB3aWxsIHN0b3JlIHRoZSBkZWxldGUgZXZlbnQgbGlzdGVuZXJzXG4gICAgICAgKiBAcHJvcGVydHkge0FycmF5fSBsaXN0ZW5lcnMuYmVmb3JlY29tbWl0IFRoaXMgd2lsbCBzdG9yZSB0aGUgYmVmb3JlY29tbWl0IGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICogQHByb3BlcnR5IHtBcnJheX0gbGlzdGVuZXJzLmNvbW1pdCBUaGlzIHdpbGwgc3RvcmUgdGhlIGNvbW1pdCBldmVudCBsaXN0ZW5lcnNcbiAgICAgICAqIEBwcm9wZXJ0eSB7QXJyYXl9IGxpc3RlbmVycy5iZWZvcmVkaXNwYXRjaCBUaGlzIHdpbGwgc3RvcmUgdGhlIGJlZm9yZWRpc3BhdGNoIGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICogQHByb3BlcnR5IHtBcnJheX0gbGlzdGVuZXJzLmRpc3BhdGNoIFRoaXMgd2lsbCBzdG9yZSB0aGUgZGlzcGF0Y2ggZXZlbnQgbGlzdGVuZXJzXG4gICAgICAgKiBAcHJvcGVydHkge0FycmF5fSBsaXN0ZW5lcnMuZ2V0dGVyIFRoaXMgd2lsbCBzdG9yZSB0aGUgZ2V0dGVyIGV2ZW50IGxpc3RlbmVyc1xuICAgICAgICogQHByb3BlcnR5IHtBcnJheX0gbGlzdGVuZXJzLmFkZGxpc3RlbmVyIFRoaXMgd2lsbCBzdG9yZSB0aGUgYWRkbGlzdGVuZXIgZXZlbnQgbGlzdGVuZXJzXG4gICAgICAgKiBAcHJvcGVydHkge0FycmF5fSBsaXN0ZW5lcnMucmVtb3ZlbGlzdGVuZXIgVGhpcyB3aWxsIHN0b3JlIHRoZSByZW1vdmVsaXN0ZW5lciBldmVudCBsaXN0ZW5lcnNcbiAgICAgICAqIEBwcm9wZXJ0eSB7QXJyYXl9IGxpc3RlbmVycy5wbHVnaW4gVGhpcyB3aWxsIHN0b3JlIHRoZSBwbHVnaW4gZXZlbnQgbGlzdGVuZXJzXG4gICAgICAgKiBAcHJvcGVydHkge0FycmF5fSBsaXN0ZW5lcnMucmVnaXN0ZXJNb2R1bGUgVGhpcyB3aWxsIHN0b3JlIHRoZSByZWdpc3Rlck1vZHVsZSBldmVudCBsaXN0ZW5lcnNcbiAgICAgICAqIEBwcm9wZXJ0eSB7QXJyYXl9IGxpc3RlbmVycy51bnJlZ2lzdGVyTW9kdWxlIFRoaXMgd2lsbCBzdG9yZSB0aGUgdW5yZWdpc3Rlck1vZHVsZSBldmVudCBsaXN0ZW5lcnNcbiAgICAgICAqL1xuICAgICAgbGlzdGVuZXJzOiB7XG4gICAgICAgIHNldDogW10sXG4gICAgICAgIGRlbGV0ZTogW10sXG4gICAgICAgIGJlZm9yZWNvbW1pdDogW10sXG4gICAgICAgIGNvbW1pdDogW10sXG4gICAgICAgIGJlZm9yZWRpc3BhdGNoOiBbXSxcbiAgICAgICAgZGlzcGF0Y2g6IFtdLFxuICAgICAgICBnZXR0ZXI6IFtdLFxuICAgICAgICBhZGRsaXN0ZW5lcjogW10sXG4gICAgICAgIHJlbW92ZWxpc3RlbmVyOiBbXSxcbiAgICAgICAgcGx1Z2luOiBbXSxcbiAgICAgICAgcmVnaXN0ZXJNb2R1bGU6IFtdLFxuICAgICAgICB1bnJlZ2lzdGVyTW9kdWxlOiBbXVxuICAgICAgfSxcblxuICAgICAgLyoqXG4gICAgICAgKiBTZXQgcGFzc2VkIGdldHRlcnMgZm9yIGZ1dHVyZSByZWZlcmVuY2VcbiAgICAgICAqIEB0eXBlIHtPYmplY3R9XG4gICAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuaW5pdC5nZXR0ZXJzXG4gICAgICAgKi9cbiAgICAgIGdldHRlcnM6IGdldHRlcnMgfHwge30sXG5cbiAgICAgIC8qKlxuICAgICAgICogU2V0IHBhc3NlZCBtdXRhdGlvbnMgZm9yIGZ1dHVyZSByZWZlcmVuY2VcbiAgICAgICAqIEB0eXBlIHtPYmplY3R9XG4gICAgICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuaW5pdC5tdXRhdGlvbnNcbiAgICAgICAqL1xuICAgICAgbXV0YXRpb25zOiBtdXRhdGlvbnMgfHwge30sXG4gICAgICAvKipcbiAgICAgICAqIFNldCBwYXNzZWQgYWN0aW9ucyBmb3IgZnV0dXJlIHJlZmVyZW5jZVxuICAgICAgICogQHR5cGUge09iamVjdH1cbiAgICAgICAqIEBuYW1lIERyYWdvbmJpbmRlci5pbml0LmFjdGlvbnNcbiAgICAgICAqL1xuICAgICAgYWN0aW9uczogYWN0aW9ucyB8fCB7fVxuICAgIH1cbiAgfSk7XG5cbiAgLyoqXG4gICAqIFdlIGNyZWF0ZSBhIHByb3h5IGZvciB0aGUgZ2V0dGVyc1xuICAgKiBAdHlwZSB7T2JqZWN0fVxuICAgKiBAbmFtZSBEcmFnb25iaW5kZXIuZ2V0dGVyc1xuICAgKi9cbiAgdGhpcy5nZXR0ZXJzID0gbmV3IFByb3h5KGdldHRlcnMgfHwge30sIHtcbiAgICAvKipcbiAgICAgKiBXaGVuIHdlIHRyeSB0byBnZXQgYSBwcm9wZXJ0eSBvZiB0aGUgZ2V0dGVyIHdlIHdpbGwgY2FsbCB0aGUgb3JpZ2luYWxcbiAgICAgKiBnZXR0ZXIgbWV0aG9kIHBhc3NpbmcgdGhlIHN0YXRlIGFzIGZpcnN0IGFyZ3VtZW50IGFuZCB0aGUgb3RoZXIgZ2V0dGVycyBhcyBzZWNvbmRcbiAgICAgKiBpZiB3ZSB0cnkgdG8gZ2V0IGEgbm9uIGV4aXN0ZW50IGdldHRlciBpdCB3aWxsIGZhaWwgc2lsZW50bHkgYXMgaWZcbiAgICAgKiB3ZSB3ZXJlIHRyeWluZyB0byBnZXQgYW4gdW5kZWZpbmVkIHByb3BlcnR5XG4gICAgICpcbiAgICAgKiBAdHlwZSB7RnVuY3Rpb259XG4gICAgICogQG5hbWUgRHJhZ29uYmluZGVyLmdldHRlcnMuZ2V0XG4gICAgICogQHBhcmFtIHtPYmplY3R9IGdldHRlcnMgVGhlIGdldHRlcnMgb2JqZWN0XG4gICAgICogQHBhcmFtIHtTdHJpbmd9IGdldHRlciBUaGUgbmFtZSBvZiB0aGUgZ2V0dGVyIHRvIHJlYWRcbiAgICAgKiBAcmV0dXJucyB7QW55fSBUaGUgdmFsdWUgb2YgdGhlIGdldHRlclxuICAgICAqL1xuICAgIGdldDogKGdldHRlcnMsIGdldHRlcikgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgbGV0IHsgc3RvcmUsIGtleSB9ID0gdGhpcy5nZXRTdG9yZSh0aGlzLCBnZXR0ZXIpO1xuXG4gICAgICAgIGlmIChzdG9yZSBpbnN0YW5jZW9mIERyYWdvbmJpbmRlciAmJiBzdG9yZS5pbml0LmdldHRlcnNba2V5XSkge1xuICAgICAgICAgIGxldCB2YWx1ZSA9IHN0b3JlLmluaXQuZ2V0dGVyc1trZXldKFxuICAgICAgICAgICAgc3RvcmUuc3RhdGUsXG4gICAgICAgICAgICBzdG9yZS5nZXR0ZXJzLFxuICAgICAgICAgICAgdGhpcy5zdGF0ZSxcbiAgICAgICAgICAgIHRoaXMuZ2V0dGVyc1xuICAgICAgICAgICk7XG4gICAgICAgICAgaWYgKHRoaXMubmFtZXNwYWNlKSB7XG4gICAgICAgICAgICBnZXR0ZXIgPSBgJHt0aGlzLm5hbWVzcGFjZX0uJHtnZXR0ZXJ9YDtcbiAgICAgICAgICB9XG4gICAgICAgICAgKHRoaXMucm9vdFN0b3JlIHx8IHRoaXMpLnRyaWdnZXIoXCJnZXR0ZXJcIiwgZ2V0dGVyLCB2YWx1ZSk7XG4gICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgfVxuICB9KTtcblxuICAvKipcbiAgICogSWYgdGhpcyBpcyBhIHN0b3JlIGJlZW4gYXR0YWNoZWQgdG8gYW5vdGhlciBzdG9yZVxuICAgKiB0aGlzIHdpbGwgaGF2ZSB0aGUgcm9vdFN0b3JlIGZvciBmdXR1cmUgcmVmZXJlbmNlXG4gICAqXG4gICAqIEB0eXBlIHsoRHJhZ29uYmluZGVyfE51bGwpfVxuICAgKiBAbmFtZSBEcmFnb25iaW5kZXIucm9vdFN0b3JlXG4gICAqL1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgXCJyb290U3RvcmVcIiwge1xuICAgIHZhbHVlOiByb290U3RvcmUgfHwgbnVsbCxcbiAgICBlbnVtZXJhYmxlOiB0cnVlXG4gIH0pO1xuXG4gIC8qKlxuICAgKiBJZiB0aGlzIGlzIGEgc3RvcmUgYmVlbiBhdHRhY2hlZCB0byBhbm90aGVyIHN0b3JlXG4gICAqIHRoaXMgd2lsbCBoYXZlIHRoZSBuYW1lc3BhY2UgYXR0YWNoZWRcbiAgICpcbiAgICogQHR5cGUgeyhTdHJpbmd8TnVsbCl9XG4gICAqIEBuYW1lIERyYWdvbmJpbmRlci5uYW1lc3BhY2VcbiAgICovXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBcIm5hbWVzcGFjZVwiLCB7XG4gICAgdmFsdWU6IG5hbWVzcGFjZSB8fCBudWxsLFxuICAgIGVudW1lcmFibGU6IHRydWVcbiAgfSk7XG5cbiAgLyoqXG4gICAqIEZpbmFsbHkgd2UgYXR0YWNoIHRoZSBpbml0aWFsIG1vZHVsZXNcbiAgICovXG4gIGlmIChtb2R1bGVzKSB7XG4gICAgT2JqZWN0LmtleXMobW9kdWxlcykuZm9yRWFjaCgobmFtZXNwYWNlKSA9PiB7XG4gICAgICBsZXQgbiA9IHRoaXMubmFtZXNwYWNlID8gYCR7dGhpcy5uYW1lc3BhY2V9LiR7bmFtZXNwYWNlfWAgOiBuYW1lc3BhY2U7XG4gICAgICAodGhpcy5yb290U3RvcmUgfHwgdGhpcykucmVnaXN0ZXJNb2R1bGUobiwgbW9kdWxlc1tuYW1lc3BhY2VdKTtcbiAgICB9KTtcbiAgfVxufVxuXG5EcmFnb25iaW5kZXIucHJvdG90eXBlID0gRHJhZ29uYmluZGVyLmZuID0ge1xuICAvKipcbiAgICogVGhpcyBtZXRob2QgY2hlY2tzIGlmIGEga2V5IGV4aXN0cyBpbiB0aGUgb2JqZWN0IHByb3ZpZGVkXG4gICAqIEB0aHJvd3Mge0Vycm9yfSBUaHJvd3MgYW4gZXJyb3IgaWYgdGhlIGtleSBkb2VzIG5vdCBleGlzdHNcbiAgICogQHBhcmFtIHtTdHJpbmd9IG9iamVjdG5hbWUgVGhlIG5hbWUgb2YgdGhlIG9iamVjdCBmb3IgbG9nIHJlZmVyZW5jZVxuICAgKiBAcGFyYW0ge09iamVjdH0gb2JqZWN0IFRoZSBvYmplY3QgaW4gd2hpY2ggd2Ugd2lsbCBzZWFyY2hcbiAgICogQHBhcmFtIHtPYmplY3R9IGtleSBUaGUga2V5IHRvIHNlYXJjaCBmb3JcbiAgICogQHJldHVybnMge1ZvaWR9IC8vXG4gICAqL1xuICBrZXlFeGlzdHMob2JqZWN0bmFtZSwgb2JqZWN0LCBrZXkpIHtcbiAgICBpZiAoIW9iamVjdFtrZXldKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFRoZSAke29iamVjdG5hbWV9IFwiJHtrZXl9XCIgZG9lcyBub3QgZXhpc3RzLmApO1xuICAgIH1cbiAgfSxcblxuICAvKipcbiAgICogVGhpcyBtZXRob2QgY2hlY2tzIHRoZSBwcm92aWRlZCBjYWxsYmFjayBpcyBhIGZ1bmN0aW9uXG4gICAqIEB0aHJvd3Mge0Vycm9yfSBUaHJvd3MgYW4gZXJyb3IgaWYgdGhlIGNhbGxiYWNrIGlzIG5vdCBhIGZ1bmN0aW9uXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0eXBlIFRoZSB0eXBlIHRvIHVzZSBmb3IgbG9nIHJlZmVyZW5jZVxuICAgKiBAcGFyYW0ge09iamVjdH0gY2FsbGJhY2sgVGhlIGNhbGxiYWNrIHRvIGNoZWNrXG4gICAqIEByZXR1cm5zIHtWb2lkfSAvL1xuICAgKi9cbiAgaXNGdW5jdGlvbih0eXBlLCBjYWxsYmFjaykge1xuICAgIGlmICh0eXBlb2YgY2FsbGJhY2sgIT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBZb3UgbmVlZCB0byBwcm92aWRlIGEgdmFsaWQgZnVuY3Rpb24gYXMgJHt0eXBlfS5gKTtcbiAgICB9XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdpdmluZyBhIGRvdCBiYXNlZCBuYW1lc3BhY2UgdGhpcyBtZXRob2Qgd2lsbCBiZSB1c2VkIHRvIGZpbmQgdGhlIG1vZHVsZSB0byBiZSBjYWxsZWRcbiAgICogQHBhcmFtIHtEcmFnb25iaW5kZXJ9IHN0b3JlIFRoZSBzdG9yZSBpbnN0YW5jZSBpbiB3aGljaCBzZWFyY2ggZm9yIHRoZSBuYW1lc3BhY2VkIG1vZHVsZVxuICAgKiBAcGFyYW0ge1N0cmluZ30gbmFtZXNwYWNlIFRoZSBuYW1lc3BhY2UgdG8gc2VhcmNoXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHtzdG9yZSwga2V5fSBBbiBvYmplY3QgY29udGFpbmluZyB0aGUgZm91bmQgbW9kdWxlIGFzIGBzdG9yZWAgYW5kIHRoZSBmaW5hbCBrZXkgYXMgYGtleWAgcHJvcGVydHlcbiAgICovXG4gIGdldFN0b3JlKHN0b3JlLCBuYW1lc3BhY2UpIHtcbiAgICBsZXQga2V5ID0gbmFtZXNwYWNlO1xuXG4gICAgaWYgKGtleS5pbmRleE9mKFwiLlwiKSA+IC0xKSB7XG4gICAgICBsZXQgcGFydHMgPSBrZXkuc3BsaXQoXCIuXCIpO1xuICAgICAga2V5ID0gcGFydHMucG9wKCk7XG4gICAgICBsZXQgbW9kdWxlTmFtZSA9IHBhcnRzLmpvaW4oXCIuXCIpO1xuICAgICAgdGhpcy5rZXlFeGlzdHMoXCJtb2R1bGVcIiwgc3RvcmUuaW5pdC5tb2R1bGVzLCBtb2R1bGVOYW1lKTtcbiAgICAgIHN0b3JlID0gc3RvcmUuaW5pdC5tb2R1bGVzW21vZHVsZU5hbWVdO1xuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBzdG9yZSxcbiAgICAgIGtleVxuICAgIH07XG4gIH0sXG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIGNoZWNrcyBpZiB0aGUgc3RvcmUgaXMgaW4gYSBzdGF0ZSBhYmxlIHRvIG11dGF0ZSB0aGUgc3RhdGVcbiAgICogQHRocm93cyB7RXJyb3J9IFRocm93cyBhbiBlcnJvciBpZiB0aGUgc3RvcmUgaXMgZnJvemVuXG4gICAqIEByZXR1cm5zIHtWb2lkfSAvL1xuICAgKi9cbiAgaXNVbmZyb3plbigpIHtcbiAgICBpZiAodGhpcy5pbml0LmZyb3plbikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiWW91IG5lZWQgdG8gY29tbWl0IGEgbXV0YXRpb24gdG8gY2hhbmdlIHRoZSBzdGF0ZVwiKTtcbiAgICB9XG4gIH0sXG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIHVuZnJvemUgdGhlIHN0YXRlIGFuZCBwcm9jZXNzIGEgbXV0YXRpb25cbiAgICogQHRocm93cyBUaGlzIHdpbGwgdHJob3cgYW4gZXJyb3IgaWYgdGhlIG11dGF0aW9uIGRvZXMgbm90IGV4aXN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gbXV0YXRpb24gVGhlIG11dGF0aW9uIG5hbWUgdG8gcHJvY2Vzc1xuICAgKiBAcGFyYW0gIHsuLi5hbnl9IFthcmdzXSBUaGUgYXJndW1lbnRzIHRvIHBhc3MgdG8gdGhlIG11dGF0aW9uXG4gICAqIEByZXR1cm5zIHtWb2lkfSBWb2lkXG4gICAqL1xuICBjb21taXQobXV0YXRpb24sIC4uLmFyZ3MpIHtcbiAgICBsZXQgeyBzdG9yZSwga2V5IH0gPSB0aGlzLmdldFN0b3JlKHRoaXMsIG11dGF0aW9uKTtcbiAgICB0aGlzLmtleUV4aXN0cyhcIm11dGF0aW9uXCIsIHN0b3JlLmluaXQubXV0YXRpb25zLCBrZXkpO1xuXG4gICAgc3RvcmUuaW5pdC5mcm96ZW4gPSBmYWxzZTtcbiAgICB0aGlzLnRyaWdnZXIoXCJiZWZvcmVjb21taXRcIiwgbXV0YXRpb24sIC4uLmFyZ3MpO1xuICAgIHN0b3JlLmluaXQubXV0YXRpb25zW2tleV0oc3RvcmUuc3RhdGUsIC4uLmFyZ3MpO1xuICAgIHRoaXMudHJpZ2dlcihcImNvbW1pdFwiLCBtdXRhdGlvbiwgLi4uYXJncyk7XG4gICAgc3RvcmUuaW5pdC5mcm96ZW4gPSB0cnVlO1xuICB9LFxuXG4gIC8qKlxuICAgKiBUaGlzIG1ldGhvZCBwcm9jZXNzIGFuIGFjdGlvblxuICAgKiBAdGhyb3dzIFRoaXMgd2lsbCB0aHJvdyBhbiBlcnJvciBpZiB0aGUgYWN0aW9uIGRvZXMgbm90IGV4aXN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWN0aW9uIFRoZSBhY3Rpb24gbmFtZSB0byBwcm9jZXNzXG4gICAqIEBwYXJhbSAgey4uLmFueX0gW2FyZ3NdIFRoZSBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYWN0aW9uXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBUaGUgYWN0aW9uIGNhbGwgYXMgYSBwcm9taXNlXG4gICAqL1xuICBkaXNwYXRjaChhY3Rpb24sIC4uLmFyZ3MpIHtcbiAgICBsZXQgeyBzdG9yZSwga2V5IH0gPSB0aGlzLmdldFN0b3JlKHRoaXMsIGFjdGlvbik7XG4gICAgdGhpcy5rZXlFeGlzdHMoXCJhY3Rpb25cIiwgc3RvcmUuaW5pdC5hY3Rpb25zLCBrZXkpO1xuXG4gICAgc3RvcmUuaW5pdC5mcm96ZW4gPSBmYWxzZTtcbiAgICB0aGlzLnRyaWdnZXIoXCJiZWZvcmVkaXNwYXRjaFwiLCBhY3Rpb24sIC4uLmFyZ3MpO1xuICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoc3RvcmUuaW5pdC5hY3Rpb25zW2tleV0oc3RvcmUsIC4uLmFyZ3MpKS50aGVuKFxuICAgICAgKHJlc3VsdCkgPT4ge1xuICAgICAgICB0aGlzLnRyaWdnZXIoXCJkaXNwYXRjaFwiLCBhY3Rpb24sIC4uLmFyZ3MpO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfVxuICAgICk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIHRyaWdnZXJzIGFuIGV2ZW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSBldmVudCBUaGUgZXZlbnQgbmFtZSB0byB0cmlnZ2VyXG4gICAqIEBwYXJhbSAgey4uLmFueX0gW2FyZ3NdIFRoZSBhcmd1bWVudHMgdGhhdCB3aWxsIGJlIHBhc3NlZCB0byB0aGUgbGlzdGVuZXJcbiAgICogQHJldHVybnMge1ZvaWR9IFZvaWRcbiAgICovXG4gIHRyaWdnZXIoZXZlbnQsIC4uLmFyZ3MpIHtcbiAgICB0aGlzLmluaXQubGlzdGVuZXJzW2V2ZW50XS5mb3JFYWNoKChjYWxsYmFjaykgPT4gY2FsbGJhY2sodGhpcywgLi4uYXJncykpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBUaGlzIG1ldGhvZCBhZGRzIGFuIGV2ZW50IGxpc3RlbmVyIHRvIHRoZSBzdG9yZVxuICAgKiBAdGhyb3dzIFRoaXMgd2lsbCB0aHJvdyBhbiBlcnJvciBpZiB0aGUgbGlzdGVuZXIgaXMgbm90IGEgZnVuY3Rpb25cbiAgICogQHRocm93cyBUaGlzIHdpbGwgdGhyb3cgYW4gZXJyb3IgaWYgdGhlIGV2ZW50IGRvZXMgbm90IGV4aXN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gZXZlbnQgVGhlIGV2ZW50IHRvIHdoaWNoIHRvIGFkZCB0aGUgbGlzdGVuZXJcbiAgICogQHBhcmFtIHtGdW5jdGlvbn0gbGlzdGVuZXIgVGhlIGxpc3RlbmVyIHRvIGFkZFxuICAgKiBAcmV0dXJucyB7RnVuY3Rpb259IFRoZSBmdW5jdGlvbiB0byB1bnN1YnNjcmliZVxuICAgKi9cbiAgb24oZXZlbnQsIGxpc3RlbmVyKSB7XG4gICAgdGhpcy5pc0Z1bmN0aW9uKFwibGlzdGVuZXJcIiwgbGlzdGVuZXIpO1xuICAgIHRoaXMua2V5RXhpc3RzKFwiZXZlbnRcIiwgdGhpcy5pbml0Lmxpc3RlbmVycywgZXZlbnQpO1xuICAgIGlmICh0aGlzLmluaXQubGlzdGVuZXJzW2V2ZW50XS5pbmRleE9mKGxpc3RlbmVyKSA9PT0gLTEpIHtcbiAgICAgIHRoaXMuaW5pdC5saXN0ZW5lcnNbZXZlbnRdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgdGhpcy50cmlnZ2VyKFwiYWRkbGlzdGVuZXJcIiwgZXZlbnQsIGxpc3RlbmVyKTtcbiAgICB9XG5cbiAgICByZXR1cm4gKCkgPT4gdGhpcy5vZmYoZXZlbnQsIGxpc3RlbmVyKTtcbiAgfSxcblxuICAvKipcbiAgICogVGhpcyBtZXRob2QgcmVtb3ZlcyBhbiBldmVudCBsaXN0ZW5lciBmcm9tIHRoZSBzdG9yZVxuICAgKiBAdGhyb3dzIFRoaXMgd2lsbCB0aHJvdyBhbiBlcnJvciBpZiB0aGUgbGlzdGVuZXIgaXMgbm90IGEgZnVuY3Rpb25cbiAgICogQHRocm93cyBUaGlzIHdpbGwgdGhyb3cgYW4gZXJyb3IgaWYgdGhlIGV2ZW50IGRvZXMgbm90IGV4aXN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gZXZlbnQgVGhlIGV2ZW50IHRvIHdoaWNoIHRvIHJlbW92ZSB0aGUgbGlzdGVuZXJcbiAgICogQHBhcmFtIHtGdW5jdGlvbn0gbGlzdGVuZXIgVGhlIGxpc3RlbmVyIHRvIHJlbW92ZVxuICAgKiBAcmV0dXJucyB7Vm9pZH0gVm9pZFxuICAgKi9cbiAgb2ZmKGV2ZW50LCBsaXN0ZW5lcikge1xuICAgIHRoaXMuaXNGdW5jdGlvbihcImxpc3RlbmVyXCIsIGxpc3RlbmVyKTtcbiAgICB0aGlzLmtleUV4aXN0cyhcImV2ZW50XCIsIHRoaXMuaW5pdC5saXN0ZW5lcnMsIGV2ZW50KTtcbiAgICBsZXQgaW5kZXggPSB0aGlzLmluaXQubGlzdGVuZXJzW2V2ZW50XS5pbmRleE9mKGxpc3RlbmVyKTtcbiAgICBpZiAoaW5kZXggPiAtMSkge1xuICAgICAgdGhpcy5pbml0Lmxpc3RlbmVyc1tldmVudF0uc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgIHRoaXMudHJpZ2dlcihcInJlbW92ZWxpc3RlbmVyXCIsIGV2ZW50LCBsaXN0ZW5lcik7XG4gICAgfVxuICB9LFxuXG4gIC8qKlxuICAgKiBUaGlzIG1ldGhvZCBhZGRzIGEgcGx1Z2luIHRvIHRoZSBTdG9yZVxuICAgKiBAdGhyb3dzIFRoaXMgd2lsbCB0aHJvdyBhbiBlcnJvciBpZiB0aGUgcGx1Z2luIGlzIG5vdCBhIGZ1bmN0aW9uXG4gICAqIEBwYXJhbSB7RnVuY3Rpb259IHBsdWdpbiBUaGUgcGx1Z2luIHRvIGFkZFxuICAgKiBAcGFyYW0gey4uLmFueX0gW29wdGlvbnNdIFRoZSBvcHRpb25zIHBhc3NlZCB0byB0aGUgcGx1Z2luXG4gICAqIEByZXR1cm5zIHtWb2lkfSBWb2lkXG4gICAqL1xuICB1c2UocGx1Z2luLCAuLi5vcHRpb25zKSB7XG4gICAgdGhpcy5pc0Z1bmN0aW9uKFwicGx1Z2luXCIsIHBsdWdpbik7XG4gICAgaWYgKHRoaXMuaW5pdC5wbHVnaW5zLmluZGV4T2YocGx1Z2luKSA9PT0gLTEpIHtcbiAgICAgIHBsdWdpbih0aGlzLCAuLi5vcHRpb25zKTtcbiAgICAgIHRoaXMuaW5pdC5wbHVnaW5zLnB1c2gocGx1Z2luKTtcbiAgICAgIHRoaXMudHJpZ2dlcihcInBsdWdpblwiLCBwbHVnaW4sIC4uLm9wdGlvbnMpO1xuICAgIH1cbiAgfSxcblxuICAvKipcbiAgICogVGhpcyBtZXRob2QgYWRkcyBhIG1vZHVsZSB0byB0aGUgc3RvcmVcbiAgICogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZSBUaGUgbmFtZXNwYWNlIHRvIGF0dGFjaCB0aGUgbW9kdWxlXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBtb2R1bGUgVGhlIG1vZHVsZSBkZWZpbml0aW9uXG4gICAqIEByZXR1cm5zIHtWb2lkfSBWb2lkXG4gICAqL1xuICByZWdpc3Rlck1vZHVsZShuYW1lc3BhY2UsIG1vZHVsZSkge1xuICAgIGxldCByb290U3RvcmUgPSB0aGlzO1xuXG4gICAgaWYgKHJvb3RTdG9yZS5pbml0Lm1vZHVsZXNbbmFtZXNwYWNlXSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgQSBtb2R1bGUgd2l0aCB0aGUgbmFtZXNwYWNlIFwiJHtuYW1lc3BhY2V9XCIgaXMgYWxyZWFkeSByZWdpc3RlcmVkLmBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgbGV0IG5ld1N0b3JlID0gbmV3IERyYWdvbmJpbmRlcihcbiAgICAgIE9iamVjdC5hc3NpZ24oeyByb290U3RvcmUsIG5hbWVzcGFjZSB9LCBtb2R1bGUpXG4gICAgKTtcblxuICAgIHJvb3RTdG9yZS5pbml0LmZyb3plbiA9IGZhbHNlO1xuICAgIHJvb3RTdG9yZS5pbml0Lm1vZHVsZXNbbmFtZXNwYWNlXSA9IG5ld1N0b3JlO1xuICAgIHJvb3RTdG9yZS5zdGF0ZVtuYW1lc3BhY2VdID0gbmV3U3RvcmUuc3RhdGU7XG4gICAgcm9vdFN0b3JlLmluaXQuZnJvemVuID0gdHJ1ZTtcbiAgICByb290U3RvcmUudHJpZ2dlcihcInJlZ2lzdGVyTW9kdWxlXCIsIG5hbWVzcGFjZSwgbW9kdWxlLCBuZXdTdG9yZSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFRoaXMgbWV0aG9kIHJlbW92ZXMgYSBtb2R1bGUgZnJvbSB0aGUgc3RvcmVcbiAgICogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZSBUaGUgbmFtZXNwYWNlIG9mIHRoZSBhdHRhY2hlZCBtb2R1bGVcbiAgICogQHJldHVybnMge1ZvaWR9IFZvaWRcbiAgICovXG4gIHVucmVnaXN0ZXJNb2R1bGUobmFtZXNwYWNlKSB7XG4gICAgbGV0IHJvb3RTdG9yZSA9IHRoaXM7XG4gICAgbGV0IHN0b3JlID0gcm9vdFN0b3JlLmluaXQubW9kdWxlc1tuYW1lc3BhY2VdO1xuXG4gICAgaWYgKHN0b3JlKSB7XG4gICAgICBzdG9yZS5pbml0LmNoaWxkTW9kdWxlTmFtZXNwYWNlcy5mb3JFYWNoKChuKSA9PlxuICAgICAgICByb290U3RvcmUudW5yZWdpc3Rlck1vZHVsZShgJHtuYW1lc3BhY2V9LiR7bn1gKVxuICAgICAgKTtcblxuICAgICAgcm9vdFN0b3JlLmluaXQuZnJvemVuID0gZmFsc2U7XG4gICAgICBkZWxldGUgcm9vdFN0b3JlLmluaXQubW9kdWxlc1tuYW1lc3BhY2VdO1xuICAgICAgZGVsZXRlIHJvb3RTdG9yZS5zdGF0ZVtuYW1lc3BhY2VdO1xuICAgICAgcm9vdFN0b3JlLmluaXQuZnJvemVuID0gdHJ1ZTtcbiAgICAgIHJvb3RTdG9yZS50cmlnZ2VyKFwidW5yZWdpc3Rlck1vZHVsZVwiLCBuYW1lc3BhY2UsIHN0b3JlKTtcbiAgICB9XG4gIH1cbn07XG5cbkRyYWdvbmJpbmRlci5mbiA9IERyYWdvbmJpbmRlci5wcm90b3R5cGU7XG5cbmlmICh0eXBlb2YgZXhwb3J0cyA9PT0gXCJvYmplY3RcIikge1xuICBtb2R1bGUuZXhwb3J0cyA9IERyYWdvbmJpbmRlcjtcbn0gZWxzZSB7XG4gICh0eXBlb2Ygd2luZG93ID09PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogd2luZG93KS5EcmFnb25iaW5kZXIgPSBEcmFnb25iaW5kZXI7XG59XG4iXSwibWFwcGluZ3MiOiJNQUtBLFNBQUFBLEVBQW9CQyxHQUNsQixHQUFtQixpQkFBUkEsR0FBNEIsT0FBUkEsSUFBaUJDLE9BQU9DLFNBQVNGLEdBQU0sQ0FDcEUsR0FBSUcsTUFBTUMsUUFBUUosR0FDaEIsUUFBU0ssRUFBSSxFQUFHQyxFQUFJTixFQUFJTyxPQUFRRixFQUFJQyxFQUFHRCxJQUNyQ04sRUFBV0MsRUFBSUssUUFFWixDQUNMLElBQUlHLEVBQVFDLFFBQVFDLFFBQVFWLEdBQzVCLFFBQVNLLEVBQUksRUFBR0MsRUFBSUUsRUFBTUQsT0FBUUYsRUFBSUMsRUFBR0QsSUFDdkNOLEVBQVdDLEVBQUlRLEVBQU1ILEtBR3pCSixPQUFPVSxPQUFPWCxHQUdoQixPQUFPQSxFQWdCVCxTQUFBWSxHQUFzQkMsTUFDcEJBLEVBQUFDLFVBQ0FBLEVBQUFDLFFBQ0FBLEVBQUFDLFFBQ0FBLEVBQUFDLFFBQ0FBLEVBQUFDLGFBQ0FBLEdBQWUsRUFBQUMsVUFDZkEsRUFBQUMsVUFDQUEsR0FDRSxJQUVGLElBQUlDLEVBQThCLG1CQUFWUixFQUF1QkEsSUFBVUEsRUFPekRTLEtBQUtULE1BQVEsSUFBSVUsTUFBTUYsR0FBYyxHQUFJLENBV3ZDRyxJQUFLLENBQUNDLEVBQU9DLEtBQ05KLEtBQUtGLFdBQWFFLE1BQU1LLEtBQUtWLFFBQVFTLEdBQ2pDRCxFQUFNQyxHQUVYUixFQUNLbkIsRUFBVzBCLEVBQU1DLElBR25CRCxFQUFNQyxHQWNmRSxJQUFLLENBQUNILEVBQU9DLEVBQU1HLEtBQ2pCUCxLQUFLUSxhQUNMLElBQUlDLEVBQU1OLEVBQU1DLEdBTWhCLE9BTEFELEVBQU1DLEdBQVFHLEVBQ1ZQLEtBQUtILFlBQ1BPLEVBQU8sR0FBR0osS0FBS0gsYUFBYU8sTUFFN0JKLEtBQUtGLFdBQWFFLE1BQU1VLFFBQVEsTUFBT04sRUFBTUcsRUFBT0UsSUFDOUMsR0FhVEUsZUFBZ0IsQ0FBQ1IsRUFBT0MsS0FDdEJKLEtBQUtRLGFBQ0wsSUFBSUMsRUFBTU4sRUFBTUMsR0FNaEIsY0FMT0QsRUFBTUMsR0FDVEosS0FBS0gsWUFDUE8sRUFBTyxHQUFHSixLQUFLSCxhQUFhTyxNQUU3QkosS0FBS0YsV0FBYUUsTUFBTVUsUUFBUSxTQUFVTixFQUFNSyxJQUMxQyxLQVVYOUIsT0FBT2lDLGVBQWVaLEtBQU0sT0FBUSxDQUNsQ08sTUFBTyxDQU9MTSxRQUFRLEVBT1JDLFFBQVMsR0FPVG5CLFFBQVMsR0FPVG9CLHNCQUF1QnBDLE9BQU9xQyxLQUFLckIsR0FBVyxJQW1COUNzQixVQUFXLENBQ1RYLElBQUssR0FDTFksT0FBUSxHQUNSQyxhQUFjLEdBQ2RDLE9BQVEsR0FDUkMsZUFBZ0IsR0FDaEJDLFNBQVUsR0FDVkMsT0FBUSxHQUNSQyxZQUFhLEdBQ2JDLGVBQWdCLEdBQ2hCQyxPQUFRLEdBQ1JDLGVBQWdCLEdBQ2hCQyxpQkFBa0IsSUFRcEJsQyxRQUFTQSxHQUFXLEdBT3BCRixVQUFXQSxHQUFhLEdBTXhCQyxRQUFTQSxHQUFXLE1BU3hCTyxLQUFLTixRQUFVLElBQUlPLE1BQU1QLEdBQVcsR0FBSSxDQWF0Q1EsSUFBSyxDQUFDMkIsRUFBU04sS0FDYixJQUNFLElBQUlPLE1BQUVBLEVBQUFDLElBQU9BLEdBQVEvQixLQUFLZ0MsU0FBU2hDLEtBQU11QixHQUV6QyxHQUFJTyxhQUFpQnhDLEdBQWdCd0MsRUFBTXpCLEtBQUtYLFFBQVFxQyxHQUFNLENBQzVELElBQUl4QixFQUFRdUIsRUFBTXpCLEtBQUtYLFFBQVFxQyxHQUM3QkQsRUFBTXZDLE1BQ051QyxFQUFNcEMsUUFDTk0sS0FBS1QsTUFDTFMsS0FBS04sU0FNUCxPQUpJTSxLQUFLSCxZQUNQMEIsRUFBUyxHQUFHdkIsS0FBS0gsYUFBYTBCLE1BRS9CdkIsS0FBS0YsV0FBYUUsTUFBTVUsUUFBUSxTQUFVYSxFQUFRaEIsR0FDNUNBLEdBRVgsTUFBUzBCLEdBQ1AsV0FZTnRELE9BQU9pQyxlQUFlWixLQUFNLFlBQWEsQ0FDdkNPLE1BQU9ULEdBQWEsS0FDcEJvQyxZQUFZLElBVWR2RCxPQUFPaUMsZUFBZVosS0FBTSxZQUFhLENBQ3ZDTyxNQUFPVixHQUFhLEtBQ3BCcUMsWUFBWSxJQU1WdkMsR0FDRmhCLE9BQU9xQyxLQUFLckIsR0FBU3dDLFFBQVNDLElBQzVCLElBQUlDLEVBQUlyQyxLQUFLSCxVQUFZLEdBQUdHLEtBQUtILGFBQWF1QyxJQUFjQSxHQUMzRHBDLEtBQUtGLFdBQWFFLE1BQU0yQixlQUFlVSxFQUFHMUMsRUFBUXlDLE1BNE56RCxHQXZOQTlDLEVBQWFnRCxVQUFZaEQsRUFBYWlELEdBQUssQ0FTekNDLFVBQVVDLEVBQVlDLEVBQVFYLEdBQzVCLElBQUtXLEVBQU9YLEdBQ1YsTUFBTSxJQUFJWSxNQUFNLE9BQU9GLE1BQWVWLHdCQVcxQ2EsV0FBV0MsRUFBTUMsR0FDZixHQUF3QixtQkFBYkEsRUFDVCxNQUFNLElBQUlILE1BQU0sMkNBQTJDRSxPQVUvRGIsU0FBU0YsRUFBT2pDLEdBQ2QsSUFBSWtDLEVBQU1sQyxFQUVWLEdBQUlrQyxFQUFJZ0IsUUFBUSxNQUFPLEVBQUksQ0FDekIsSUFBSUMsRUFBUWpCLEVBQUlrQixNQUFNLEtBQ3RCbEIsRUFBTWlCLEVBQU1FLE1BQ1osSUFBSUMsRUFBYUgsRUFBTUksS0FBSyxLQUM1QnBELEtBQUt3QyxVQUFVLFNBQVVWLEVBQU16QixLQUFLVixRQUFTd0QsR0FDN0NyQixFQUFRQSxFQUFNekIsS0FBS1YsUUFBUXdELEdBRzdCLE1BQU8sQ0FDTHJCLFFBQ0FDLFFBU0p2QixhQUNFLEdBQUlSLEtBQUtLLEtBQUtRLE9BQ1osTUFBTSxJQUFJOEIsTUFBTSxzREFXcEJ2QixPQUFPaUMsS0FBYUMsR0FDbEIsSUFBSXhCLE1BQUVBLEVBQUFDLElBQU9BLEdBQVEvQixLQUFLZ0MsU0FBU2hDLEtBQU1xRCxHQUN6Q3JELEtBQUt3QyxVQUFVLFdBQVlWLEVBQU16QixLQUFLYixVQUFXdUMsR0FFakRELEVBQU16QixLQUFLUSxRQUFTLEVBQ3BCYixLQUFLVSxRQUFRLGVBQWdCMkMsS0FBYUMsR0FDMUN4QixFQUFNekIsS0FBS2IsVUFBVXVDLEdBQUtELEVBQU12QyxTQUFVK0QsR0FDMUN0RCxLQUFLVSxRQUFRLFNBQVUyQyxLQUFhQyxHQUNwQ3hCLEVBQU16QixLQUFLUSxRQUFTLEdBVXRCUyxTQUFTaUMsS0FBV0QsR0FDbEIsSUFBSXhCLE1BQUVBLEVBQUFDLElBQU9BLEdBQVEvQixLQUFLZ0MsU0FBU2hDLEtBQU11RCxHQUt6QyxPQUpBdkQsS0FBS3dDLFVBQVUsU0FBVVYsRUFBTXpCLEtBQUtaLFFBQVNzQyxHQUU3Q0QsRUFBTXpCLEtBQUtRLFFBQVMsRUFDcEJiLEtBQUtVLFFBQVEsaUJBQWtCNkMsS0FBV0QsR0FDbkNFLFFBQVFDLFFBQVEzQixFQUFNekIsS0FBS1osUUFBUXNDLEdBQUtELEtBQVV3QixJQUFPSSxLQUM3REMsSUFDQzNELEtBQUtVLFFBQVEsV0FBWTZDLEtBQVdELEdBQzdCSyxLQVdiakQsUUFBUWtELEtBQVVOLEdBQ2hCdEQsS0FBS0ssS0FBS1ksVUFBVTJDLEdBQU96QixRQUFTVyxHQUFhQSxFQUFTOUMsUUFBU3NELEtBV3JFTyxHQUFHRCxFQUFPRSxHQVFSLE9BUEE5RCxLQUFLNEMsV0FBVyxXQUFZa0IsR0FDNUI5RCxLQUFLd0MsVUFBVSxRQUFTeEMsS0FBS0ssS0FBS1ksVUFBVzJDLElBQ1EsSUFBakQ1RCxLQUFLSyxLQUFLWSxVQUFVMkMsR0FBT2IsUUFBUWUsS0FDckM5RCxLQUFLSyxLQUFLWSxVQUFVMkMsR0FBT0csS0FBS0QsR0FDaEM5RCxLQUFLVSxRQUFRLGNBQWVrRCxFQUFPRSxJQUc5QixJQUFNOUQsS0FBS2dFLElBQUlKLEVBQU9FLElBVy9CRSxJQUFJSixFQUFPRSxHQUNUOUQsS0FBSzRDLFdBQVcsV0FBWWtCLEdBQzVCOUQsS0FBS3dDLFVBQVUsUUFBU3hDLEtBQUtLLEtBQUtZLFVBQVcyQyxHQUM3QyxJQUFJSyxFQUFRakUsS0FBS0ssS0FBS1ksVUFBVTJDLEdBQU9iLFFBQVFlLEdBQzNDRyxHQUFRLElBQ1ZqRSxLQUFLSyxLQUFLWSxVQUFVMkMsR0FBT00sT0FBT0QsRUFBTyxHQUN6Q2pFLEtBQUtVLFFBQVEsaUJBQWtCa0QsRUFBT0UsS0FXMUNLLElBQUl6QyxLQUFXMEMsR0FDYnBFLEtBQUs0QyxXQUFXLFNBQVVsQixJQUNnQixJQUF0QzFCLEtBQUtLLEtBQUtTLFFBQVFpQyxRQUFRckIsS0FDNUJBLEVBQU8xQixRQUFTb0UsR0FDaEJwRSxLQUFLSyxLQUFLUyxRQUFRaUQsS0FBS3JDLEdBQ3ZCMUIsS0FBS1UsUUFBUSxTQUFVZ0IsS0FBVzBDLEtBVXRDekMsZUFBZTlCLEVBQVd3RSxHQUN4QixJQUFJdkUsRUFBWUUsS0FFaEIsR0FBSUYsRUFBVU8sS0FBS1YsUUFBUUUsR0FDekIsTUFBTSxJQUFJOEMsTUFDUixnQ0FBZ0M5Qyw2QkFJcEMsSUFBSXlFLEVBQVcsSUFBSWhGLEVBQ2pCWCxPQUFPNEYsT0FBTyxDQUFFekUsWUFBV0QsYUFBYXdFLElBRzFDdkUsRUFBVU8sS0FBS1EsUUFBUyxFQUN4QmYsRUFBVU8sS0FBS1YsUUFBUUUsR0FBYXlFLEVBQ3BDeEUsRUFBVVAsTUFBTU0sR0FBYXlFLEVBQVMvRSxNQUN0Q08sRUFBVU8sS0FBS1EsUUFBUyxFQUN4QmYsRUFBVVksUUFBUSxpQkFBa0JiLEVBQVd3RSxFQUFRQyxJQVF6RDFDLGlCQUFpQi9CLEdBQ2YsSUFBSUMsRUFBWUUsS0FDWjhCLEVBQVFoQyxFQUFVTyxLQUFLVixRQUFRRSxHQUUvQmlDLElBQ0ZBLEVBQU16QixLQUFLVSxzQkFBc0JvQixRQUFTRSxHQUN4Q3ZDLEVBQVU4QixpQkFBaUIsR0FBRy9CLEtBQWF3QyxNQUc3Q3ZDLEVBQVVPLEtBQUtRLFFBQVMsU0FDakJmLEVBQVVPLEtBQUtWLFFBQVFFLFVBQ3ZCQyxFQUFVUCxNQUFNTSxHQUN2QkMsRUFBVU8sS0FBS1EsUUFBUyxFQUN4QmYsRUFBVVksUUFBUSxtQkFBb0JiLEVBQVdpQyxNQUt2RHhDLEVBQWFpRCxHQUFLakQsRUFBYWdELFVBRVIsaUJBQVprQyxRQUNULElBQUFDLEVBQU9uRixPQUVZLG9CQUFYb0YsT0FBeUJDLE9BQVNELFFBQVFwRixhQUFlQSxFIn0= -------------------------------------------------------------------------------- /dist/dragonbinder.mjs: -------------------------------------------------------------------------------- 1 | var __getOwnPropNames = Object.getOwnPropertyNames; 2 | var __commonJS = (cb, mod) => function __require() { 3 | return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 4 | }; 5 | 6 | // lib/index.js 7 | var require_lib = __commonJS({ 8 | "lib/index.js"(exports, module) { 9 | function deepFreeze(obj) { 10 | if (typeof obj === "object" && obj !== null && !Object.isFrozen(obj)) { 11 | if (Array.isArray(obj)) { 12 | for (let i = 0, l = obj.length; i < l; i++) { 13 | deepFreeze(obj[i]); 14 | } 15 | } else { 16 | let props = Reflect.ownKeys(obj); 17 | for (let i = 0, l = props.length; i < l; i++) { 18 | deepFreeze(obj[props[i]]); 19 | } 20 | } 21 | Object.freeze(obj); 22 | } 23 | return obj; 24 | } 25 | function Dragonbinder({ 26 | state, 27 | mutations, 28 | actions, 29 | getters, 30 | modules, 31 | shouldFreeze = true, 32 | namespace, 33 | rootStore 34 | } = {}) { 35 | let localState = typeof state === "function" ? state() : state; 36 | this.state = new Proxy(localState || {}, { 37 | get: (state2, prop) => { 38 | if ((this.rootStore || this).init.modules[prop]) { 39 | return state2[prop]; 40 | } 41 | if (shouldFreeze) { 42 | return deepFreeze(state2[prop]); 43 | } 44 | return state2[prop]; 45 | }, 46 | set: (state2, prop, value) => { 47 | this.isUnfrozen(); 48 | let old = state2[prop]; 49 | state2[prop] = value; 50 | if (this.namespace) { 51 | prop = `${this.namespace}.${prop}`; 52 | } 53 | (this.rootStore || this).trigger("set", prop, value, old); 54 | return true; 55 | }, 56 | deleteProperty: (state2, prop) => { 57 | this.isUnfrozen(); 58 | let old = state2[prop]; 59 | delete state2[prop]; 60 | if (this.namespace) { 61 | prop = `${this.namespace}.${prop}`; 62 | } 63 | (this.rootStore || this).trigger("delete", prop, old); 64 | return true; 65 | } 66 | }); 67 | Object.defineProperty(this, "init", { 68 | value: { 69 | frozen: true, 70 | plugins: [], 71 | modules: {}, 72 | childModuleNamespaces: Object.keys(modules || {}), 73 | listeners: { 74 | set: [], 75 | delete: [], 76 | beforecommit: [], 77 | commit: [], 78 | beforedispatch: [], 79 | dispatch: [], 80 | getter: [], 81 | addlistener: [], 82 | removelistener: [], 83 | plugin: [], 84 | registerModule: [], 85 | unregisterModule: [] 86 | }, 87 | getters: getters || {}, 88 | mutations: mutations || {}, 89 | actions: actions || {} 90 | } 91 | }); 92 | this.getters = new Proxy(getters || {}, { 93 | get: (getters2, getter) => { 94 | try { 95 | let { store, key } = this.getStore(this, getter); 96 | if (store instanceof Dragonbinder && store.init.getters[key]) { 97 | let value = store.init.getters[key](store.state, store.getters, this.state, this.getters); 98 | if (this.namespace) { 99 | getter = `${this.namespace}.${getter}`; 100 | } 101 | (this.rootStore || this).trigger("getter", getter, value); 102 | return value; 103 | } 104 | } catch (error) { 105 | return; 106 | } 107 | } 108 | }); 109 | Object.defineProperty(this, "rootStore", { 110 | value: rootStore || null, 111 | enumerable: true 112 | }); 113 | Object.defineProperty(this, "namespace", { 114 | value: namespace || null, 115 | enumerable: true 116 | }); 117 | if (modules) { 118 | Object.keys(modules).forEach((namespace2) => { 119 | let n = this.namespace ? `${this.namespace}.${namespace2}` : namespace2; 120 | (this.rootStore || this).registerModule(n, modules[namespace2]); 121 | }); 122 | } 123 | } 124 | Dragonbinder.prototype = Dragonbinder.fn = { 125 | keyExists(objectname, object, key) { 126 | if (!object[key]) { 127 | throw new Error(`The ${objectname} "${key}" does not exists.`); 128 | } 129 | }, 130 | isFunction(type, callback) { 131 | if (typeof callback !== "function") { 132 | throw new Error(`You need to provide a valid function as ${type}.`); 133 | } 134 | }, 135 | getStore(store, namespace) { 136 | let key = namespace; 137 | if (key.indexOf(".") > -1) { 138 | let parts = key.split("."); 139 | key = parts.pop(); 140 | let moduleName = parts.join("."); 141 | this.keyExists("module", store.init.modules, moduleName); 142 | store = store.init.modules[moduleName]; 143 | } 144 | return { 145 | store, 146 | key 147 | }; 148 | }, 149 | isUnfrozen() { 150 | if (this.init.frozen) { 151 | throw new Error("You need to commit a mutation to change the state"); 152 | } 153 | }, 154 | commit(mutation, ...args) { 155 | let { store, key } = this.getStore(this, mutation); 156 | this.keyExists("mutation", store.init.mutations, key); 157 | store.init.frozen = false; 158 | this.trigger("beforecommit", mutation, ...args); 159 | store.init.mutations[key](store.state, ...args); 160 | this.trigger("commit", mutation, ...args); 161 | store.init.frozen = true; 162 | }, 163 | dispatch(action, ...args) { 164 | let { store, key } = this.getStore(this, action); 165 | this.keyExists("action", store.init.actions, key); 166 | store.init.frozen = false; 167 | this.trigger("beforedispatch", action, ...args); 168 | return Promise.resolve(store.init.actions[key](store, ...args)).then((result) => { 169 | this.trigger("dispatch", action, ...args); 170 | return result; 171 | }); 172 | }, 173 | trigger(event, ...args) { 174 | this.init.listeners[event].forEach((callback) => callback(this, ...args)); 175 | }, 176 | on(event, listener) { 177 | this.isFunction("listener", listener); 178 | this.keyExists("event", this.init.listeners, event); 179 | if (this.init.listeners[event].indexOf(listener) === -1) { 180 | this.init.listeners[event].push(listener); 181 | this.trigger("addlistener", event, listener); 182 | } 183 | return () => this.off(event, listener); 184 | }, 185 | off(event, listener) { 186 | this.isFunction("listener", listener); 187 | this.keyExists("event", this.init.listeners, event); 188 | let index = this.init.listeners[event].indexOf(listener); 189 | if (index > -1) { 190 | this.init.listeners[event].splice(index, 1); 191 | this.trigger("removelistener", event, listener); 192 | } 193 | }, 194 | use(plugin, ...options) { 195 | this.isFunction("plugin", plugin); 196 | if (this.init.plugins.indexOf(plugin) === -1) { 197 | plugin(this, ...options); 198 | this.init.plugins.push(plugin); 199 | this.trigger("plugin", plugin, ...options); 200 | } 201 | }, 202 | registerModule(namespace, module2) { 203 | let rootStore = this; 204 | if (rootStore.init.modules[namespace]) { 205 | throw new Error(`A module with the namespace "${namespace}" is already registered.`); 206 | } 207 | let newStore = new Dragonbinder(Object.assign({ rootStore, namespace }, module2)); 208 | rootStore.init.frozen = false; 209 | rootStore.init.modules[namespace] = newStore; 210 | rootStore.state[namespace] = newStore.state; 211 | rootStore.init.frozen = true; 212 | rootStore.trigger("registerModule", namespace, module2, newStore); 213 | }, 214 | unregisterModule(namespace) { 215 | let rootStore = this; 216 | let store = rootStore.init.modules[namespace]; 217 | if (store) { 218 | store.init.childModuleNamespaces.forEach((n) => rootStore.unregisterModule(`${namespace}.${n}`)); 219 | rootStore.init.frozen = false; 220 | delete rootStore.init.modules[namespace]; 221 | delete rootStore.state[namespace]; 222 | rootStore.init.frozen = true; 223 | rootStore.trigger("unregisterModule", namespace, store); 224 | } 225 | } 226 | }; 227 | Dragonbinder.fn = Dragonbinder.prototype; 228 | if (typeof exports === "object") { 229 | module.exports = Dragonbinder; 230 | } else { 231 | (typeof window === "undefined" ? global : window).Dragonbinder = Dragonbinder; 232 | } 233 | } 234 | }); 235 | export default require_lib(); 236 | -------------------------------------------------------------------------------- /docma.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": ["./lib/index.js", "./README.md", "./CONTRIBUTING.md"], 3 | "dest": "./docs", 4 | "jsdoc": { 5 | "package": true, 6 | "access": "all" 7 | }, 8 | "app": { 9 | "entrance": "content:readme", 10 | "server": "github", 11 | "base": "/dragonbinder" 12 | }, 13 | "template": { 14 | "path": "zebra", 15 | "options": { 16 | "title": { 17 | "label": "Dragonbinder", 18 | "href": "/dragonbinder" 19 | }, 20 | "sidebar": { 21 | "enabled": true, 22 | "outline": "tree", 23 | "collapsed": false, 24 | "toolbar": true, 25 | "itemsFolded": false, 26 | "itemsOverflow": "crop", 27 | "badges": true, 28 | "search": true, 29 | "animations": true 30 | }, 31 | "symbols": { 32 | "autoLink": true, 33 | "params": "list", 34 | "enums": "list", 35 | "props": "list", 36 | "meta": false 37 | }, 38 | "contentView": { 39 | "bookmarks": "h1,h2,h3", 40 | "faLibs": "all", 41 | "faVersion": "5.8.1" 42 | }, 43 | "navbar": { 44 | "enabled": true, 45 | "dark": true, 46 | "animations": true, 47 | "menu": [ 48 | { 49 | "label": "API", 50 | "iconClass": "fas fa-book", 51 | "href": "./?api" 52 | }, 53 | { 54 | "label": "Contributing", 55 | "iconClass": "fas fa-code-branch", 56 | "href": "./?content=contributing" 57 | }, 58 | { 59 | "label": "NPM", 60 | "iconClass": "fab fa-npm", 61 | "href": "https://www.npmjs.com/package/dragonbinder", 62 | "target": "_blank" 63 | }, 64 | { 65 | "label": "GitHub", 66 | "iconClass": "fab fa-github", 67 | "href": "https://github.com/Masquerade-Circus/dragonbinder", 68 | "target": "_blank" 69 | } 70 | ] 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /docs/content/contributing.html: -------------------------------------------------------------------------------- 1 | 2 |

Contributing Guide

3 |
4 |

Table of Contents

5 |
6 | 26 |

Ways to contribute

27 |
28 |

Call for contributors

29 |

You can follow the tag of call for contributors in the issues. The pull requests solving these issues are most likely to be merged.

30 |

Submit Feedback

31 |

The feedback should be submitted by creating an issue at GitHub issues. Select the related template and add the corresponding labels.

32 |

Fix Bugs

33 |

You may look through the GitHub issues for bugs. Anything tagged with bug report is open to whoever wants to implement it.

34 |

Implement Features

35 |

You may look through the GitHub issues for feature requests. Anything tagged with feature request is open to whoever wants to implement it.

36 |

Write Documentation

37 |

The documentation is either directly written into the Markdown files, or automatically extracted from the jsdoc comments by executing the yarn docs script, this will make use of docma to generate the documentation.

38 |

In the first situation, you only need to change the markdown file and if its a new file, add it to the docma.json configuration.

39 |

In the second situation, you need to change the jsdoc comments of the file that needs the documentation.

40 |

Develop a plugin

41 |

You may develop a plugin creating your own repo. Just give your plugin a meaningful unique name, make sure the name follows the convention dragonbinder-plugin-{name} and, when finished, you can make a pull request updating the documentation with your plugin listed in the Plugin system section.

42 |

Pull Request Guide

43 |
44 |

Before you submit a pull request, check that it meets these guidelines. Please also read Code Style Guide, Documentation Guide and Testing Guide to ensure your merge request meet our requirements.

45 |
    46 |
  • Fork the repository. Create a new branch from the master branch. Give your new branch a meaningful name.
  • 47 |
  • Create a draft pull request from your new branch to the master branch of the original repo. Give your pull request a meaningful name.
  • 48 |
  • Include resolves #issue_number in the description of the pull request if needed and briefly describe your contribution.
  • 49 |
  • Submit the pull request from the first day of your development (after your first commit).
  • 50 |
  • When the contribution is complete, make sure the pull request passed the CI tests. Change the draft pull request status to ready for review. Set the reviewer to @masquerade-circus.
  • 51 |
  • For the case of bug fixes, add new test cases which would fail before your bug fix.
  • 52 |
53 |

Code Style Guide

54 |
55 |

This project use eslint with sonarjs eslint plugin and prettier to format the code. Make sure you have configured your editor to format the files using this rules on save. Consider that jsdoc comments are not mandatory, but we need them to build a trustworthy documentation.

56 |

Documentation Guide

57 |
58 |

The documentation should be provided in two ways, jsdoc comments and updating the readme file. We prefer the documentation to be as complete as possible.

59 |

You only need to add tutorials to your code if you are contributing or updating a new feature.

60 |

If you are updating via jsdoc comments or updating the readme file, you can make use of the yarn docs:watch script to watch for changes to the lib and the readme file. This script will rebuild the documentation on every change.

61 |

To see the documentation locally, you can make use of the yarn docs:serve, this will serve the documentation at http://localhost:9000.

62 |

Finally, before commit make use of remark to format any updated markdown file. After format the updated files, build the documentation one last time using the yarn docs script.

63 |

Testing Guide

64 |
65 |

We make use of AVA to write tests. You should test your code by writing unit testing code in tests directory. The testing file name should be a {feature}_test.js file in the corresponding directory. The suffix _test is mandatory.

66 |

You can make use of the yarn dev:test:nyc script to watch for changes and run the tests on every change. It would output the coverage information to the console and into a directory named coverage. Please make sure the code coverage percentage does not decrease after your contribution, otherwise, the code will not be merged.

67 |

Attribution

68 |
69 |

This contribution guide is adapted partially from the Auto-Keras contributing guide.

70 | -------------------------------------------------------------------------------- /docs/content/readme.html: -------------------------------------------------------------------------------- 1 | 2 |

npm version 3 | 4 |

5 |

Build Status 6 | 7 | 8 | Codacy Badge 9 | Coverage Status 10 | License

11 |

Dragonbinder

12 |
13 |

1kb progressive state management library inspired by Vuex.

14 |

Features

15 |
16 |
    17 |
  • Immutable state.
  • 18 |
  • Getters.
  • 19 |
  • Mutations.
  • 20 |
  • Actions.
  • 21 |
  • Event listeners.
  • 22 |
  • Nested modules.
  • 23 |
  • Plugin system.
  • 24 |
25 |

Table of Contents

26 |
27 | 59 |

Install

60 |
61 |

You can get this library as a Node.js module available through the npm registry:

62 |
// With npm
 63 | $ npm install dragonbinder
 64 | // With yarn
 65 | $ yarn add dragonbinder
66 |

Or you can use it standalone in the browser with: 67 | <script src="https://cdn.jsdelivr.net/npm/dragonbinder"></script>

68 |

Use

69 |
70 |
const Dragonbinder = require('dragonbinder');
 71 | 
 72 | const store = new Dragonbinder({
 73 |   state: {
 74 |     count: 0
 75 |   },
 76 |   mutations: {
 77 |     increment(state) {
 78 |       state.count++
 79 |     }
 80 |   }
 81 | });
 82 | 
 83 | store.commit('increment');
 84 | console.log(store.state.count) // -> 1
85 |

State

86 |

Dragonbinder use Proxies to create a state as a "single source of truth" which cannot be changed unless you commit a mutation. 87 | This 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.

88 |

If you don't provide an initial state by the state property Dragonbinder will create one.

89 |
const store = new Dragonbinder({
 90 |   state: {
 91 |     count: 0
 92 |   },
 93 |   mutations: {
 94 |     addProperty(state, value) {
 95 |       state.hello = 'world';
 96 |     },
 97 |     modifyProperty(state) {
 98 |       state.count++
 99 |     },
100 |     removeProperty(state) {
101 |       delete state.count;
102 |     }
103 |   }
104 | });
105 | 
106 | // This will throw errors
107 | store.state.hello = 'world';
108 | store.state.count++;
109 | delete state.count;
110 | 
111 | // This will work as expected
112 | store.commit('addProperty');
113 | store.commit('modifyProperty');
114 | store.commit('removeProperty');
115 |

Also, if you want to avoid singletons to reuse your initial store definition, you can declare its state as a factory function.

116 |
const myStoreDefinition = {
117 |   state(){
118 |     return {
119 |       count: 1
120 |     }
121 |   },
122 |   mutations: {
123 |     increment(state, payload) {
124 |       state.count = state.count + payload;
125 |     }
126 |   }
127 | };
128 | 
129 | const store1 = new Dragonbinder(myStoreDefinition);
130 | const store2 = new Dragonbinder(myStoreDefinition);
131 | 
132 | store1.commit('increment', 5);
133 | store2.commit('increment', 3);
134 | 
135 | console.log(store1.state.count); // -> 6
136 | console.log(store2.state.count); // -> 4
137 |

Getters

138 |

As with Vue, with Dragonbinder you can create getters to create computed properties based on the state. 139 | This getters will receive the state as first argument and all other getters as second.

140 |
const store = new Dragonbinder({
141 |   state: {
142 |     todos: [
143 |       {
144 |         content: 'First',
145 |         completed: false
146 |       }, 
147 |       {
148 |         content: 'Second',
149 |         completed: true
150 |       }
151 |     ]
152 |   },
153 |   getters: {
154 |     completed(state){
155 |       return state.todos.filter(item => item.completed);
156 |     },
157 |     completedCount(state, getters){
158 |       return getters.completed.length;
159 |     }
160 |   }
161 | });
162 | 
163 | console.log(store.getters.completed); // -> { content: 'Second', completed: true }
164 | console.log(store.getters.completedCount); // -> 1
165 |

Mutations

166 |

Mutations are the only way to change the state and you must consider the next points when designing mutations.

167 |
    168 |
  • Following the Vuex pattern, mutations must be synchronous.
  • 169 |
  • Unlike many other libraries you can pass any number of arguments to a mutation.
  • 170 |
  • 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.
  • 171 |
172 |
const store = new Dragonbinder({
173 |   state: {
174 |     hello: {
175 |       name: 'John Doe'
176 |     }
177 |   },
178 |   mutations: {
179 |     changeNameError(state, payload){
180 |       state.hello.name = payload;
181 |     },
182 |     changeNameOk(state, payload){
183 |       state.hello = {...state.hello, name: payload};
184 |     },
185 |     changeNameTo(state, ...args){
186 |       state.hello = {...state.hello, name: args.join(' ')};
187 |     }
188 |   }
189 | });
190 | 
191 | // This will throw an assign to read only property error
192 | store.commit('changeNameError', 'Jane Doe');
193 | 
194 | // This will work as expected
195 | store.commit('changeNameOk', 'Jane Doe');
196 | 
197 | // You can pass any number of arguments as payload
198 | store.commit('changeNameTo', 'Jane', 'Doe');
199 |

Actions

200 |

If you need to handle async functions you must use actions. And actions will always return a promise as result of calling them.

201 |
const store = new Dragonbinder({
202 |   state: {
203 |     count: 0
204 |   },
205 |   mutations: {
206 |     increment(state) {
207 |       state.count++
208 |     }
209 |   },
210 |   actions: {
211 |     increment(state){
212 |       return new Promise((resolve) => {
213 |         setTimeout(() => {
214 |           store.commit('increment');
215 |           resolve();
216 |         }, 1000);
217 |       })
218 |     }
219 |   }
220 | });
221 | 
222 | store.dispatch('increment').then(() => console.log(store.state.count)); // -> 1 after one second
223 |

Events

224 |

You can register/unregister callbacks to events.

225 |
const store = new Dragonbinder({
226 |   state: {
227 |     count: 0
228 |   },
229 |   mutations: {
230 |     increment(state) {
231 |       state.count++
232 |     }
233 |   }
234 | });
235 | 
236 | // Add a named listener
237 | let namedListener = (store, prop, newVal, oldVal) => console.log(`The property ${prop} was changed from ${oldVal} to ${newVal}`);
238 | store.on('set', namedListener);
239 | 
240 | // Add an anonymous listener
241 | let removeAnonymousListener = store.on('set', () => console.log('Anonymous listener triggered'));
242 | 
243 | // Committing increment will trigger the listener
244 | store.commit('increment');
245 | // $ The property count was changed from 0 to 1
246 | // $ Anonymous listener triggered
247 | 
248 | // Remove a named listener 
249 | store.off('set', namedListener);
250 | 
251 | // Remove an anonyous listener 
252 | removeAnonymousListener();
253 | 
254 | // Committing increment will do nothing as the listeners are already removed
255 | store.commit('increment'); 
256 |

Event types

257 |

All events receive the store instance as the first argument.

258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 |
Event nameIts called whenArguments received by place
addlistenerAn event listener is addedEvent name | Listener added
removelistenerAn event listener is removedEvent name | Listener removed
setA property of the state is added or modified, also triggered when a module is registeredProperty name | New value | Old value
deleteA property of the state is deleted, also triggered when a module is unregisteredProperty name | Old value
beforecommitCommit method called and before apply the mutationMutation name | (...) Arguments passed to the mutation
commitCommit method called and after apply the mutationMutation name | (...) Arguments passed to the mutation
beforedispatchDispatch method called and before apply the actionAction name | (...) Arguments passed to the action
dispatchDispatch method called and after apply the actionAction name | (...) Arguments passed to the action
getterA getter is calledGetter name | Value of the getter
pluginA plugin is addedPlugin added | (...) Options passed to the plugin
registerModuleA module is registeredNamespace registered | Module definition | Store created with the definition
unregisterModuleA module is unregisteredNamespace unregistered | Store created with the definition
327 |

Nested modules

328 |

Like Vuex, Dragonbinder allows you to divide your store into modules and each module can contain its own store definition including more nested modules.

329 |
const moduleA = {
330 |   state: { ... },
331 |   mutations: { ... },
332 |   actions: { ... },
333 |   getters: { ... }
334 | }
335 | 
336 | const moduleB = {
337 |   state: { ... },
338 |   mutations: { ... },
339 |   actions: { ... },
340 |   getters: { ... }
341 |   modules: {
342 |     a: moduleA
343 |   }
344 | }
345 | 
346 | const store = new Dragonbinder({
347 |   modules: {
348 |     b: moduleB
349 |   }
350 | });
351 | 
352 | console.log(store.state.b) // -> `moduleB`'s state
353 | console.log(store.state['b.a']) // -> `moduleA`'s state
354 |

Also, after the store is created you can register/unregister modules with the registerModule and unregisterModule methods.

355 |

Consider that when you unregister a module, only its initial nested modules will be unregistered with it.

356 |
const moduleA = {
357 |   state: { ... },
358 |   mutations: { ... },
359 |   actions: { ... },
360 |   getters: { ... }
361 | }
362 | 
363 | const moduleB = {
364 |   state: { ... },
365 |   mutations: { ... },
366 |   actions: { ... },
367 |   getters: { ... },
368 |   modules: {
369 |     a: moduleA
370 |   }
371 | }
372 | 
373 | const moduleC = {
374 |   state: { ... },
375 |   mutations: { ... },
376 |   actions: { ... },
377 |   getters: { ... }
378 | }
379 | 
380 | const store = new Dragonbinder();
381 | store.registerModule('b', moduleB);
382 | store.registerModule('b.c', moduleC);
383 | 
384 | console.log(store.state.b) // -> `moduleB`'s state
385 | console.log(store.state['b.a']) // -> `moduleA`'s state
386 | console.log(store.state['b.c']) // -> `moduleC`'s state
387 | 
388 | store.unregisterModule('b'); 
389 | 
390 | console.log(store.state.b) // -> undefined
391 | console.log(store.state['b.a']) // -> undefined
392 | console.log(store.state['b.c']) // -> `moduleC`'s state
393 |

Local and root state

394 |

Each 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.

395 |

The 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.

396 |

Getters will get the root state and root getters as the third and fourth arguments.

397 |

Actions will access the root context by the rootStore property of the local context.

398 |
const moduleA = {
399 |   state: {
400 |     hello: 'world'
401 |   },
402 |   mutations: {
403 |     sayHello(state, payload){
404 |       state.hello = payload;
405 |     }
406 |   },
407 |   actions:{
408 |     change(store, payload){
409 |       store.commit('sayHello', payload);
410 |       store.rootStore.commit('increment');
411 |     }
412 |   },
413 |   getters: {
414 |     hello(state, getters, rootState, rootGetters){
415 |       return `You have said hello ${rootState.count} times to ${state.hello}`;
416 |     }
417 |   }
418 | };
419 | 
420 | const store = new Dragonbinder({
421 |   state: {
422 |     count: 0
423 |   },
424 |   mutations: {
425 |     increment(state){
426 |       state.count++;
427 |     }
428 |   },
429 |   modules: {
430 |     a: moduleA
431 |   }
432 | });
433 | 
434 | store.dispatch('a.change', 'John Doe');
435 | console.log(store.getters['a.hello']); // -> You have said hello 1 times to John Doe
436 | console.log(store.state.count) // -> 1
437 | console.log(store.state.a.hello) // -> John Doe
438 |

Plugin system

439 |

Dragonbinder comes with a simple but powerfull plugin system. 440 | You can extend its core functionality or change it completely by making use of plugins.

441 |

Using plugins

442 |
let store = new Dragonbinder();
443 | store.use(myPlugin, ...options);
444 |

Developing plugins

445 |

A Dragonbinder plugin is a module that exports a single function that will be called 446 | with the store instance as first argument and optionally with the passed options if any.

447 |
const Dragonbinder = require('dragonbinder');
448 | const myPlugin = (store, ...options) => {
449 | 
450 |   Dragonbinder.myGlobalMethod = function() {
451 |     // Awesome code here
452 |   };
453 | 
454 |   Dragonbinder.fn.myPrototypeMethod = function() {
455 |     // Awesome code here
456 |   };
457 | 
458 |   store.myLocalMethod = function() {
459 |     // Awesome code here
460 |   };
461 | };
462 |

API

463 |
464 |

Check the docs at: https://masquerade-circus.github.io/dragonbinder/?api

465 |

Contributing

466 |
467 |

Check the contributing guide at: https://masquerade-circus.github.io/dragonbinder/?content=contributing

468 |

Development, Build and Tests

469 |
470 |
    471 |
  • Use yarn dev to watch and compile the library on every change to it.
  • 472 |
  • Use yarn build to build the library.
  • 473 |
  • Use yarn test to run tests only once.
  • 474 |
  • Use yarn dev:test to run the tests watching changes to library and tests.
  • 475 |
  • Use yarn dev:test:nyc to run the tests watching changes and get the test coverage at last.
  • 476 |
  • Use yarn docs to build the documentation.
  • 477 |
  • Use yarn docs:watch to watch and rebuild the documentation on every change to the library or the md files.
  • 478 |
  • Use yarn docs:serve to see the generated documentation locally.
  • 479 |
480 | 481 |
482 |

Author: Masquerade Circus. License Apache-2.0

483 | -------------------------------------------------------------------------------- /docs/css/docma.css: -------------------------------------------------------------------------------- 1 | img.docma{display:inline-block;border:0 none}img.docma.emoji,img.docma.emoji-sm,img.docma.emoji-1x{height:1em;width:1em;margin:0 .05em 0 .1em;vertical-align:-0.1em}img.docma.emoji-md{height:1.33em;width:1.33em;margin:0 .0665em 0 .133em;vertical-align:-0.133em}img.docma.emoji-lg{height:1.66em;width:1.66em;margin:0 .083em 0 .166em;vertical-align:-0.166em}img.docma .emoji-2x{height:2em;width:2em;margin:0 .1em 0 .2em;vertical-align:-0.2em}img.docma .emoji-3x{height:3em;width:3em;margin:0 .15em 0 .3em;vertical-align:-0.3em}img.docma .emoji-4x{height:4em;width:4em;margin:0 .2em 0 .4em;vertical-align:-0.4em}img.docma .emoji-5x{height:5em;width:5em;margin:0 .25em 0 .5em;vertical-align:-0.5em}ul.docma.task-list{list-style:none;padding-left:0;margin-left:0}ul.docma.task-list>li.docma.task-item{padding-left:0;margin-left:0}.docma-hide,.docma-ignore{position:absolute !important;display:none !important;visibility:hidden !important;opacity:0 !important;margin-right:2000px} -------------------------------------------------------------------------------- /docs/img/tree-deep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-deep.png -------------------------------------------------------------------------------- /docs/img/tree-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-first.png -------------------------------------------------------------------------------- /docs/img/tree-folded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-folded.png -------------------------------------------------------------------------------- /docs/img/tree-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-last.png -------------------------------------------------------------------------------- /docs/img/tree-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-node.png -------------------------------------------------------------------------------- /docs/img/tree-parent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-parent.png -------------------------------------------------------------------------------- /docs/img/tree-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Masquerade-Circus/dragonbinder/b6dfe5bd52c378922720455b40a2468ba51f1c89/docs/img/tree-space.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("less",function(e){var r="[\\w-]+",t="("+r+"|@{"+r+"})",a=[],c=[],s=function(e){return{cN:"string",b:"~?"+e+".*?"+e}},b=function(e,r,t){return{cN:e,b:r,r:t}},n={b:"\\(",e:"\\)",c:c,r:0};c.push(e.CLCM,e.CBCM,s("'"),s('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},b("number","#[0-9A-Fa-f]+\\b"),n,b("variable","@@?"+r,10),b("variable","@{"+r+"}"),b("built_in","~?`[^`]*?`"),{cN:"attribute",b:r+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=c.concat({b:"{",e:"}",c:a}),o={bK:"when",eW:!0,c:[{bK:"and not"}].concat(c)},u={b:t+"\\s*:",rB:!0,e:"[;}]",r:0,c:[{cN:"attribute",b:t,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",r:0,c:c}}]},l={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:c,r:0}},C={cN:"variable",v:[{b:"@"+r+"\\s*:",r:15},{b:"@"+r}],starts:{e:"[;}]",rE:!0,c:i}},p={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:t,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",r:0,c:[e.CLCM,e.CBCM,o,b("keyword","all\\b"),b("variable","@{"+r+"}"),b("selector-tag",t+"%?",0),b("selector-id","#"+t),b("selector-class","\\."+t,0),b("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return a.push(e.CLCM,e.CBCM,l,C,u,p),{cI:!0,i:"[=>'/<($\"]",c:a}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"number",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{eW:!0,eE:!0,c:[r,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"meta",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[i,r,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[i,e.QSM,e.ASM,r,e.CSSNM,{b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("dust",function(e){var t="if eq ne lt lte gt gte select default math sep";return{aliases:["dst"],cI:!0,sL:"xml",c:[{cN:"template-tag",b:/\{[#\/]/,e:/\}/,i:/;/,c:[{cN:"name",b:/[a-zA-Z\.-]+/,starts:{eW:!0,r:0,c:[e.QSM]}}]},{cN:"template-variable",b:/\{/,e:/\}/,i:/;/,k:t}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"};return{aliases:["ts"],k:r,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:["self",e.CLCM,e.CBCM]}]}]}],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/%/,r:0},{bK:"constructor",e:/\{/,eE:!0,c:["self",{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}]},{b:/module\./,k:{built_in:"module"},r:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"meta",b:"@[A-Za-z]+"}]}}); -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This method is used to deep freeze an object 3 | * @param {Object} obj The object to freeze 4 | * @returns {Object} Object frozen 5 | */ 6 | function deepFreeze(obj) { 7 | if (typeof obj === "object" && obj !== null && !Object.isFrozen(obj)) { 8 | if (Array.isArray(obj)) { 9 | for (let i = 0, l = obj.length; i < l; i++) { 10 | deepFreeze(obj[i]); 11 | } 12 | } else { 13 | let props = Reflect.ownKeys(obj); 14 | for (let i = 0, l = props.length; i < l; i++) { 15 | deepFreeze(obj[props[i]]); 16 | } 17 | } 18 | Object.freeze(obj); 19 | } 20 | 21 | return obj; 22 | } 23 | 24 | /** 25 | * This is the Store entity 26 | * 27 | * @constructor 28 | * @param {Object} data The initial options to create the store 29 | * @param {Object} [data.state] Optional initial state for the store 30 | * @param {Object} [data.mutations] Optional mutation methods to update the state 31 | * @param {Object} [data.actions] Optional actions to handle async methods and update the state 32 | * @param {Object} [data.getters] Optional getters to read from the state 33 | * @param {Object} [data.modules] Optional modules to register to this store 34 | * @param {Boolean} [data.shouldFreeze=true] Whether to freeze the state 35 | * @returns {Object} new store object 36 | */ 37 | function Dragonbinder({ 38 | state, 39 | mutations, 40 | actions, 41 | getters, 42 | modules, 43 | shouldFreeze = true, 44 | namespace, 45 | rootStore 46 | } = {}) { 47 | // Initialize the localState for this store 48 | let localState = typeof state === "function" ? state() : state; 49 | 50 | /** 51 | * We create a proxy for the state 52 | * @type {Object} 53 | * @name Dragonbinder.state 54 | */ 55 | this.state = new Proxy(localState || {}, { 56 | /** 57 | * Every time we try to access a property from the state we try to deep freeze the property 58 | * to prevent direct modifications to the state 59 | * 60 | * @type {Object} 61 | * @name Dragonbinder.state.get 62 | * @param {Object} state The state object 63 | * @param {String} prop The property to read 64 | * @returns {Any} The property value 65 | */ 66 | get: (state, prop) => { 67 | if ((this.rootStore || this).init.modules[prop]) { 68 | return state[prop]; 69 | } 70 | if (shouldFreeze) { 71 | return deepFreeze(state[prop]); 72 | } 73 | 74 | return state[prop]; 75 | }, 76 | 77 | /** 78 | * If the user tries to set directly it will throw an error, only if we have unfrozen the state via commit 79 | * this will proceed to set the value 80 | * 81 | * @type {Object} 82 | * @name Dragonbinder.state.set 83 | * @param {Object} state The state object 84 | * @param {String} prop The property to set 85 | * @param {Any} value The property value 86 | * @returns {Boolean} Always returns true 87 | */ 88 | set: (state, prop, value) => { 89 | this.isUnfrozen(); 90 | let old = state[prop]; 91 | state[prop] = value; 92 | if (this.namespace) { 93 | prop = `${this.namespace}.${prop}`; 94 | } 95 | (this.rootStore || this).trigger("set", prop, value, old); 96 | return true; 97 | }, 98 | 99 | /** 100 | * If the user tries to delete directly it will throw an error, only if we have unfrozen the state via commit 101 | * this will proceed to delete the property 102 | * 103 | * @type {Object} 104 | * @name Dragonbinder.state.deleteProperty 105 | * @param {Object} state The state object 106 | * @param {String} prop The property to delete 107 | * @returns {Boolean} Always returns true 108 | */ 109 | deleteProperty: (state, prop) => { 110 | this.isUnfrozen(); 111 | let old = state[prop]; 112 | delete state[prop]; 113 | if (this.namespace) { 114 | prop = `${this.namespace}.${prop}`; 115 | } 116 | (this.rootStore || this).trigger("delete", prop, old); 117 | return true; 118 | } 119 | }); 120 | 121 | /** 122 | * We will define a hidden property to store the init data 123 | * 124 | * @type {Object} 125 | * @name Dragonbinder.init 126 | */ 127 | Object.defineProperty(this, "init", { 128 | value: { 129 | /** 130 | * This property will store the frozen state of the store 131 | * 132 | * @type {Boolean} 133 | * @name Dragonbinder.init.frozen 134 | */ 135 | frozen: true, 136 | 137 | /** 138 | * This property will store the plugins 139 | * @type {Array} 140 | * @name Dragonbinder.init.plugins 141 | */ 142 | plugins: [], 143 | 144 | /** 145 | * This property will store the registered modules 146 | * @type {Object} 147 | * @name Dragonbinder.init.modules 148 | */ 149 | modules: {}, 150 | 151 | /** 152 | * This will have the initial child module namespace definitions 153 | * @type {Array} 154 | * @name Dragonbinder.init.childModuleNamespaces 155 | */ 156 | childModuleNamespaces: Object.keys(modules || {}), 157 | 158 | /** 159 | * This property will store the event listeners 160 | * @type {Object} 161 | * @name Dragonbinder.init.listeners 162 | * @property {Array} listeners.set This will store the set event listeners 163 | * @property {Array} listeners.delete This will store the delete event listeners 164 | * @property {Array} listeners.beforecommit This will store the beforecommit event listeners 165 | * @property {Array} listeners.commit This will store the commit event listeners 166 | * @property {Array} listeners.beforedispatch This will store the beforedispatch event listeners 167 | * @property {Array} listeners.dispatch This will store the dispatch event listeners 168 | * @property {Array} listeners.getter This will store the getter event listeners 169 | * @property {Array} listeners.addlistener This will store the addlistener event listeners 170 | * @property {Array} listeners.removelistener This will store the removelistener event listeners 171 | * @property {Array} listeners.plugin This will store the plugin event listeners 172 | * @property {Array} listeners.registerModule This will store the registerModule event listeners 173 | * @property {Array} listeners.unregisterModule This will store the unregisterModule event listeners 174 | */ 175 | listeners: { 176 | set: [], 177 | delete: [], 178 | beforecommit: [], 179 | commit: [], 180 | beforedispatch: [], 181 | dispatch: [], 182 | getter: [], 183 | addlistener: [], 184 | removelistener: [], 185 | plugin: [], 186 | registerModule: [], 187 | unregisterModule: [] 188 | }, 189 | 190 | /** 191 | * Set passed getters for future reference 192 | * @type {Object} 193 | * @name Dragonbinder.init.getters 194 | */ 195 | getters: getters || {}, 196 | 197 | /** 198 | * Set passed mutations for future reference 199 | * @type {Object} 200 | * @name Dragonbinder.init.mutations 201 | */ 202 | mutations: mutations || {}, 203 | /** 204 | * Set passed actions for future reference 205 | * @type {Object} 206 | * @name Dragonbinder.init.actions 207 | */ 208 | actions: actions || {} 209 | } 210 | }); 211 | 212 | /** 213 | * We create a proxy for the getters 214 | * @type {Object} 215 | * @name Dragonbinder.getters 216 | */ 217 | this.getters = new Proxy(getters || {}, { 218 | /** 219 | * When we try to get a property of the getter we will call the original 220 | * getter method passing the state as first argument and the other getters as second 221 | * if we try to get a non existent getter it will fail silently as if 222 | * we were trying to get an undefined property 223 | * 224 | * @type {Function} 225 | * @name Dragonbinder.getters.get 226 | * @param {Object} getters The getters object 227 | * @param {String} getter The name of the getter to read 228 | * @returns {Any} The value of the getter 229 | */ 230 | get: (getters, getter) => { 231 | try { 232 | let { store, key } = this.getStore(this, getter); 233 | 234 | if (store instanceof Dragonbinder && store.init.getters[key]) { 235 | let value = store.init.getters[key]( 236 | store.state, 237 | store.getters, 238 | this.state, 239 | this.getters 240 | ); 241 | if (this.namespace) { 242 | getter = `${this.namespace}.${getter}`; 243 | } 244 | (this.rootStore || this).trigger("getter", getter, value); 245 | return value; 246 | } 247 | } catch (error) { 248 | return; 249 | } 250 | } 251 | }); 252 | 253 | /** 254 | * If this is a store been attached to another store 255 | * this will have the rootStore for future reference 256 | * 257 | * @type {(Dragonbinder|Null)} 258 | * @name Dragonbinder.rootStore 259 | */ 260 | Object.defineProperty(this, "rootStore", { 261 | value: rootStore || null, 262 | enumerable: true 263 | }); 264 | 265 | /** 266 | * If this is a store been attached to another store 267 | * this will have the namespace attached 268 | * 269 | * @type {(String|Null)} 270 | * @name Dragonbinder.namespace 271 | */ 272 | Object.defineProperty(this, "namespace", { 273 | value: namespace || null, 274 | enumerable: true 275 | }); 276 | 277 | /** 278 | * Finally we attach the initial modules 279 | */ 280 | if (modules) { 281 | Object.keys(modules).forEach((namespace) => { 282 | let n = this.namespace ? `${this.namespace}.${namespace}` : namespace; 283 | (this.rootStore || this).registerModule(n, modules[namespace]); 284 | }); 285 | } 286 | } 287 | 288 | Dragonbinder.prototype = Dragonbinder.fn = { 289 | /** 290 | * This method checks if a key exists in the object provided 291 | * @throws {Error} Throws an error if the key does not exists 292 | * @param {String} objectname The name of the object for log reference 293 | * @param {Object} object The object in which we will search 294 | * @param {Object} key The key to search for 295 | * @returns {Void} // 296 | */ 297 | keyExists(objectname, object, key) { 298 | if (!object[key]) { 299 | throw new Error(`The ${objectname} "${key}" does not exists.`); 300 | } 301 | }, 302 | 303 | /** 304 | * This method checks the provided callback is a function 305 | * @throws {Error} Throws an error if the callback is not a function 306 | * @param {String} type The type to use for log reference 307 | * @param {Object} callback The callback to check 308 | * @returns {Void} // 309 | */ 310 | isFunction(type, callback) { 311 | if (typeof callback !== "function") { 312 | throw new Error(`You need to provide a valid function as ${type}.`); 313 | } 314 | }, 315 | 316 | /** 317 | * Giving a dot based namespace this method will be used to find the module to be called 318 | * @param {Dragonbinder} store The store instance in which search for the namespaced module 319 | * @param {String} namespace The namespace to search 320 | * @returns {Object} {store, key} An object containing the found module as `store` and the final key as `key` property 321 | */ 322 | getStore(store, namespace) { 323 | let key = namespace; 324 | 325 | if (key.indexOf(".") > -1) { 326 | let parts = key.split("."); 327 | key = parts.pop(); 328 | let moduleName = parts.join("."); 329 | this.keyExists("module", store.init.modules, moduleName); 330 | store = store.init.modules[moduleName]; 331 | } 332 | 333 | return { 334 | store, 335 | key 336 | }; 337 | }, 338 | 339 | /** 340 | * This method checks if the store is in a state able to mutate the state 341 | * @throws {Error} Throws an error if the store is frozen 342 | * @returns {Void} // 343 | */ 344 | isUnfrozen() { 345 | if (this.init.frozen) { 346 | throw new Error("You need to commit a mutation to change the state"); 347 | } 348 | }, 349 | 350 | /** 351 | * This method unfroze the state and process a mutation 352 | * @throws This will trhow an error if the mutation does not exists 353 | * @param {String} mutation The mutation name to process 354 | * @param {...any} [args] The arguments to pass to the mutation 355 | * @returns {Void} Void 356 | */ 357 | commit(mutation, ...args) { 358 | let { store, key } = this.getStore(this, mutation); 359 | this.keyExists("mutation", store.init.mutations, key); 360 | 361 | store.init.frozen = false; 362 | this.trigger("beforecommit", mutation, ...args); 363 | store.init.mutations[key](store.state, ...args); 364 | this.trigger("commit", mutation, ...args); 365 | store.init.frozen = true; 366 | }, 367 | 368 | /** 369 | * This method process an action 370 | * @throws This will throw an error if the action does not exists 371 | * @param {String} action The action name to process 372 | * @param {...any} [args] The arguments to pass to the action 373 | * @returns {Promise} The action call as a promise 374 | */ 375 | dispatch(action, ...args) { 376 | let { store, key } = this.getStore(this, action); 377 | this.keyExists("action", store.init.actions, key); 378 | 379 | store.init.frozen = false; 380 | this.trigger("beforedispatch", action, ...args); 381 | return Promise.resolve(store.init.actions[key](store, ...args)).then( 382 | (result) => { 383 | this.trigger("dispatch", action, ...args); 384 | return result; 385 | } 386 | ); 387 | }, 388 | 389 | /** 390 | * This method triggers an event 391 | * @param {String} event The event name to trigger 392 | * @param {...any} [args] The arguments that will be passed to the listener 393 | * @returns {Void} Void 394 | */ 395 | trigger(event, ...args) { 396 | this.init.listeners[event].forEach((callback) => callback(this, ...args)); 397 | }, 398 | 399 | /** 400 | * This method adds an event listener to the store 401 | * @throws This will throw an error if the listener is not a function 402 | * @throws This will throw an error if the event does not exists 403 | * @param {String} event The event to which to add the listener 404 | * @param {Function} listener The listener to add 405 | * @returns {Function} The function to unsubscribe 406 | */ 407 | on(event, listener) { 408 | this.isFunction("listener", listener); 409 | this.keyExists("event", this.init.listeners, event); 410 | if (this.init.listeners[event].indexOf(listener) === -1) { 411 | this.init.listeners[event].push(listener); 412 | this.trigger("addlistener", event, listener); 413 | } 414 | 415 | return () => this.off(event, listener); 416 | }, 417 | 418 | /** 419 | * This method removes an event listener from the store 420 | * @throws This will throw an error if the listener is not a function 421 | * @throws This will throw an error if the event does not exists 422 | * @param {String} event The event to which to remove the listener 423 | * @param {Function} listener The listener to remove 424 | * @returns {Void} Void 425 | */ 426 | off(event, listener) { 427 | this.isFunction("listener", listener); 428 | this.keyExists("event", this.init.listeners, event); 429 | let index = this.init.listeners[event].indexOf(listener); 430 | if (index > -1) { 431 | this.init.listeners[event].splice(index, 1); 432 | this.trigger("removelistener", event, listener); 433 | } 434 | }, 435 | 436 | /** 437 | * This method adds a plugin to the Store 438 | * @throws This will throw an error if the plugin is not a function 439 | * @param {Function} plugin The plugin to add 440 | * @param {...any} [options] The options passed to the plugin 441 | * @returns {Void} Void 442 | */ 443 | use(plugin, ...options) { 444 | this.isFunction("plugin", plugin); 445 | if (this.init.plugins.indexOf(plugin) === -1) { 446 | plugin(this, ...options); 447 | this.init.plugins.push(plugin); 448 | this.trigger("plugin", plugin, ...options); 449 | } 450 | }, 451 | 452 | /** 453 | * This method adds a module to the store 454 | * @param {String} namespace The namespace to attach the module 455 | * @param {Object} module The module definition 456 | * @returns {Void} Void 457 | */ 458 | registerModule(namespace, module) { 459 | let rootStore = this; 460 | 461 | if (rootStore.init.modules[namespace]) { 462 | throw new Error( 463 | `A module with the namespace "${namespace}" is already registered.` 464 | ); 465 | } 466 | 467 | let newStore = new Dragonbinder( 468 | Object.assign({ rootStore, namespace }, module) 469 | ); 470 | 471 | rootStore.init.frozen = false; 472 | rootStore.init.modules[namespace] = newStore; 473 | rootStore.state[namespace] = newStore.state; 474 | rootStore.init.frozen = true; 475 | rootStore.trigger("registerModule", namespace, module, newStore); 476 | }, 477 | 478 | /** 479 | * This method removes a module from the store 480 | * @param {String} namespace The namespace of the attached module 481 | * @returns {Void} Void 482 | */ 483 | unregisterModule(namespace) { 484 | let rootStore = this; 485 | let store = rootStore.init.modules[namespace]; 486 | 487 | if (store) { 488 | store.init.childModuleNamespaces.forEach((n) => 489 | rootStore.unregisterModule(`${namespace}.${n}`) 490 | ); 491 | 492 | rootStore.init.frozen = false; 493 | delete rootStore.init.modules[namespace]; 494 | delete rootStore.state[namespace]; 495 | rootStore.init.frozen = true; 496 | rootStore.trigger("unregisterModule", namespace, store); 497 | } 498 | } 499 | }; 500 | 501 | Dragonbinder.fn = Dragonbinder.prototype; 502 | 503 | if (typeof exports === "object") { 504 | module.exports = Dragonbinder; 505 | } else { 506 | (typeof window === "undefined" ? global : window).Dragonbinder = Dragonbinder; 507 | } 508 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragonbinder", 3 | "version": "2.2.1", 4 | "description": "1kb progressive state management library inspired by Vuex.", 5 | "source": "lib/index.js", 6 | "main": "dist/dragonbinder.min.js", 7 | "module": "dist/dragonbinder.mjs", 8 | "unpkg": "dist/dragonbinder.min.js", 9 | "browser": "dist/dragonbinder.min.js", 10 | "exports": { 11 | "import": "./dist/dragonbinder.mjs", 12 | "require": "./dist/dragonbinder.js" 13 | }, 14 | "repository": "git@github.com:Masquerade-Circus/dragonbinder.git", 15 | "author": "Masquerade Circus ", 16 | "license": "Apache-2.0", 17 | "private": false, 18 | "keywords": [ 19 | "store", 20 | "state", 21 | "state-management", 22 | "reactive", 23 | "vuex", 24 | "redux", 25 | "flux", 26 | "manager", 27 | "progressive" 28 | ], 29 | "engines": { 30 | "node": ">=8.15.0" 31 | }, 32 | "engineStrict": true, 33 | "scripts": { 34 | "build": "node build.js", 35 | "dev:test": "ava --watch --verbose", 36 | "dev:test:nyc": "nodemon -w ./tests -w ./lib --exec 'nyc --reporter=lcov --reporter=text ava --verbose'", 37 | "test": "npm run test:ava && npm run test:esm && npm run test:cjs && npm run test:min", 38 | "test:ava": "nyc ava", 39 | "test:esm": "node tests/test.mjs", 40 | "test:cjs": "node tests/test.cjs", 41 | "test:min": "node tests/test.min.js", 42 | "coverage": "nyc report --reporter=text-lcov | coveralls", 43 | "docs": "docma -c docma.json", 44 | "docs:watch": "nodemon -w ./lib -w ./*.md -w ./docma.json --exec 'npm run docs'", 45 | "docs:serve": "docma serve", 46 | "commit": "git add . && git-cz", 47 | "release": "release-it --verbose", 48 | "release-test": "release-it --dry-run --verbose" 49 | }, 50 | "devDependencies": { 51 | "@release-it/conventional-changelog": "^5.0.0", 52 | "ava": "^4.3.0", 53 | "coveralls": "^3.1.1", 54 | "cz-conventional-changelog": "^3.3.0", 55 | "docma": "^3.2.2", 56 | "esbuild": "^0.14.48", 57 | "eslint": "^8.19.0", 58 | "eslint-plugin-sonarjs": "^0.13.0", 59 | "esm": "^3.2.25", 60 | "expect": "^28.1.1", 61 | "nyc": "^15.1.0", 62 | "release-it": "^15.1.1", 63 | "terser": "^5.14.1" 64 | }, 65 | "overrides": { 66 | "minimist": "^1.2.6" 67 | }, 68 | "ava": { 69 | "files": [ 70 | "tests/**/*_test.js" 71 | ], 72 | "ignoredByWatcher": [ 73 | "dist", 74 | "node_modules", 75 | "docs" 76 | ], 77 | "failWithoutAssertions": false, 78 | "require": [ 79 | "esm" 80 | ] 81 | }, 82 | "nyc": { 83 | "include": [ 84 | "lib" 85 | ], 86 | "exclude": [ 87 | "build.js", 88 | "tests", 89 | "dist", 90 | "node_modules" 91 | ] 92 | }, 93 | "config": { 94 | "commitizen": { 95 | "path": "./node_modules/cz-conventional-changelog" 96 | } 97 | }, 98 | "release-it": { 99 | "plugins": { 100 | "@release-it/conventional-changelog": { 101 | "infile": "CHANGELOG.md", 102 | "preset": { 103 | "name": "conventionalcommits", 104 | "types": [ 105 | { 106 | "type": "feat", 107 | "section": "Features" 108 | }, 109 | { 110 | "type": "feature", 111 | "section": "Features" 112 | }, 113 | { 114 | "type": "fix", 115 | "section": "Bug Fixes" 116 | }, 117 | { 118 | "type": "perf", 119 | "section": "Performance Improvements" 120 | }, 121 | { 122 | "type": "revert", 123 | "section": "Reverts" 124 | }, 125 | { 126 | "type": "docs", 127 | "section": "Documentation" 128 | }, 129 | { 130 | "type": "style", 131 | "section": "Styles" 132 | }, 133 | { 134 | "type": "chore", 135 | "section": "Miscellaneous Chores" 136 | }, 137 | { 138 | "type": "refactor", 139 | "section": "Code Refactoring" 140 | }, 141 | { 142 | "type": "test", 143 | "section": "Tests" 144 | }, 145 | { 146 | "type": "build", 147 | "section": "Build System" 148 | }, 149 | { 150 | "type": "ci", 151 | "section": "Continuous Integration" 152 | } 153 | ] 154 | } 155 | } 156 | }, 157 | "git": { 158 | "requireCleanWorkingDir": false 159 | }, 160 | "github": { 161 | "release": true 162 | }, 163 | "npm": { 164 | "publish": true 165 | }, 166 | "hooks": { 167 | "before:init": [ 168 | "npm run build", 169 | "npm run test", 170 | "npm run docs" 171 | ] 172 | } 173 | }, 174 | "funding": "https://github.com/sponsors/Masquerade-Circus" 175 | } 176 | -------------------------------------------------------------------------------- /rollup.js: -------------------------------------------------------------------------------- 1 | let rollup = require('rollup'); 2 | let commonjs = require('rollup-plugin-commonjs'); 3 | let nodeResolve = require('rollup-plugin-node-resolve'); 4 | let includepaths = require('rollup-plugin-includepaths'); 5 | let filesize = require('rollup-plugin-filesize'); 6 | let progress = require('rollup-plugin-progress'); 7 | let buble = require('rollup-plugin-buble'); 8 | let sourcemaps = require('rollup-plugin-sourcemaps'); 9 | let { terser } = require('rollup-plugin-terser'); 10 | 11 | let inputOptions = { 12 | input: './lib/index.js', 13 | plugins: [ 14 | progress({ clearLine: false }), 15 | includepaths({ paths: ['./lib', './node_modules'] }), 16 | nodeResolve({ 17 | jsnext: true, 18 | main: true, 19 | browser: true 20 | }), 21 | commonjs({ 22 | include: ['./node_modules/**'], // Default: undefined 23 | // if false then skip sourceMap generation for CommonJS modules 24 | sourceMap: true // Default: true 25 | }), 26 | sourcemaps(), 27 | filesize(), 28 | buble({ target: { chrome: 70, firefox: 60, safari: 10, node: 8 } }), 29 | terser({ warnings: 'verbose', compress: {passes: 1} }) 30 | ], 31 | cache: undefined 32 | }; 33 | 34 | let outputOptions = { 35 | file: './dist/dragonbinder.min.js', 36 | format: 'iife', 37 | sourcemap: true, 38 | compact: true, 39 | name: 'Store' 40 | }; 41 | 42 | if (process.env.NODE_ENV === 'production') { 43 | rollup 44 | .rollup(inputOptions) 45 | .then((bundle) => bundle.write(outputOptions)) 46 | .then(() => console.log('Bundle finished.')); 47 | } 48 | 49 | if (process.env.NODE_ENV !== 'production') { 50 | inputOptions.output = outputOptions; 51 | inputOptions.watch = { 52 | include: ['./lib/**'], 53 | chokidar: false 54 | }; 55 | 56 | const watch = rollup.watch(inputOptions); 57 | const stderr = console.error.bind(console); 58 | 59 | watch.on('event', (event) => { 60 | switch (event.code) { 61 | case 'START': 62 | stderr('checking rollup-watch version...'); 63 | break; 64 | case 'BUNDLE_START': 65 | stderr(`bundling ${outputOptions.file}...`); 66 | break; 67 | case 'BUNDLE_END': 68 | stderr(`${outputOptions.file} bundled in ${event.duration}ms.`); 69 | break; 70 | case 'ERROR': 71 | stderr(`error: ${event.error}`); 72 | break; 73 | case 'FATAL': 74 | stderr(`error: ${event.error}`); 75 | break; 76 | case 'END': 77 | stderr(`Watching for changes...`); 78 | break; 79 | default: 80 | stderr(`unknown event: ${event}`); 81 | } 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /tests/index_test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable sonarjs/no-duplicate-string */ 2 | const test = require("ava"); 3 | const { expect } = require("expect"); 4 | const Dragonbinder = require("../lib"); 5 | 6 | function getNewStore() { 7 | let mainModule = { 8 | state: { b: [1], a: 0, c: { a: 1 } }, 9 | mutations: { 10 | pushB(state, payload) { 11 | state.b = [...state.b, payload]; 12 | }, 13 | deleteB(state) { 14 | delete state.b; 15 | }, 16 | increment(state, payload) { 17 | state.a = state.a + payload; 18 | }, 19 | changeC(state, payload) { 20 | state.c = Object.assign({}, state.c, { a: payload }); 21 | }, 22 | changeCDirectly(state, payload) { 23 | state.c.a = payload; 24 | } 25 | }, 26 | actions: { 27 | pushB(context, payload) { 28 | return new Promise((resolve) => { 29 | setTimeout(() => { 30 | context.commit("pushB", payload); 31 | resolve(); 32 | }, 1000); 33 | }); 34 | } 35 | }, 36 | getters: { 37 | length(state, getters) { 38 | return getters.items.length; 39 | }, 40 | items(state, getters) { 41 | return state.b; 42 | } 43 | } 44 | }; 45 | 46 | return new Dragonbinder(mainModule); 47 | } 48 | 49 | test("Create empty state if state is not passed", (t) => { 50 | let store = new Dragonbinder(); 51 | expect(store.state).toBeDefined(); 52 | }); 53 | 54 | test("Use deepFrozen to froze the state", (t) => { 55 | let store = new Dragonbinder({ 56 | state: { 57 | a: { 58 | b: { 59 | c: { 60 | d: null 61 | }, 62 | d: null 63 | } 64 | } 65 | } 66 | }); 67 | 68 | expect(Object.isFrozen(store.state.a.b.c.d)).toBeTruthy(); 69 | }); 70 | 71 | test("Throw error if you try to mutate the state directly", (t) => { 72 | let store = getNewStore(); 73 | expect(() => (store.state.b = 1)).toThrowError( 74 | "You need to commit a mutation to change the state" 75 | ); 76 | }); 77 | 78 | test("Throw error if you try to mutate a deeper property of the state directly", (t) => { 79 | let store = getNewStore(); 80 | expect(() => store.state.b.push(1)).toThrowError( 81 | "Cannot add property 1, object is not extensible" 82 | ); 83 | store.state.c.a = 2; 84 | expect(store.state.c.a).toEqual(1); 85 | }); 86 | 87 | test("Throw error if you try to remove a property directly", (t) => { 88 | let store = getNewStore(); 89 | expect(() => delete store.state.b).toThrowError( 90 | "You need to commit a mutation to change the state" 91 | ); 92 | }); 93 | 94 | test("Fail silently if you try to remove a deeper property directly", (t) => { 95 | let store = getNewStore(); 96 | delete store.state.c.a; 97 | expect(store.state.c.a).toEqual(1); 98 | }); 99 | 100 | test("Mutate the state by commit", (t) => { 101 | let store = getNewStore(); 102 | expect(store.state.a).toEqual(0); 103 | store.commit("increment", 1); 104 | expect(store.state.a).toEqual(1); 105 | 106 | expect(store.state.b).toEqual([1]); 107 | store.commit("deleteB"); 108 | expect(store.state.b).toBeUndefined(); 109 | }); 110 | 111 | test("Throw error if you try to commit an undefined mutation", (t) => { 112 | let store = getNewStore(); 113 | expect(() => store.commit("hello")).toThrowError( 114 | 'The mutation "hello" does not exists.' 115 | ); 116 | }); 117 | 118 | test("Mutate the state by dispatch", async (t) => { 119 | let store = getNewStore(); 120 | expect(store.state.b).toEqual([1]); 121 | await store.dispatch("pushB", 2); 122 | expect(store.state.b).toEqual([1, 2]); 123 | }); 124 | 125 | test("Throw error if you try to dispatch an undefined action", (t) => { 126 | let store = getNewStore(); 127 | expect(() => store.dispatch("hello")).toThrowError( 128 | 'The action "hello" does not exists.' 129 | ); 130 | }); 131 | 132 | test("Use a getter to obtain a property from the state", (t) => { 133 | let store = getNewStore(); 134 | expect(store.getters.items).toEqual([1]); 135 | }); 136 | 137 | test("Use a getter to obtain data using another getter", (t) => { 138 | let store = getNewStore(); 139 | expect(store.getters.length).toEqual(1); 140 | }); 141 | 142 | test("Fail silently if you try to get an undefined getter, getter must return undefined", (t) => { 143 | let store = getNewStore(); 144 | expect(store.getters.ok).toBeUndefined(); 145 | }); 146 | 147 | test("Use plugins", (t) => { 148 | let store = new Dragonbinder(); 149 | let params = []; 150 | let plugin = (...args) => { 151 | params.push(args); 152 | }; 153 | store.use(plugin, "option1", "option2"); 154 | store.use(plugin, "option1", "option2"); 155 | expect(params).toEqual([[expect.any(Dragonbinder), "option1", "option2"]]); 156 | }); 157 | 158 | test("Add a listener", (t) => { 159 | let store = getNewStore(); 160 | let count = 0; 161 | let method = () => count++; 162 | expect(count).toEqual(0); 163 | store.on("set", method); 164 | store.on("delete", method); 165 | store.commit("increment", 1); 166 | store.commit("deleteB"); 167 | expect(count).toEqual(2); 168 | }); 169 | 170 | test("Remove a named listener", (t) => { 171 | let store = getNewStore(); 172 | let count = 0; 173 | let method = () => count++; 174 | expect(count).toEqual(0); 175 | store.on("set", method); 176 | store.commit("increment"); 177 | expect(count).toEqual(1); 178 | store.off("set", method); 179 | store.commit("increment"); 180 | expect(count).toEqual(1); 181 | }); 182 | 183 | test("Remove an anonymous method by returned remove callback", (t) => { 184 | let store = getNewStore(); 185 | let count = 0; 186 | expect(count).toEqual(0); 187 | let removeListener = store.on("set", () => count++); 188 | store.commit("increment"); 189 | expect(count).toEqual(1); 190 | removeListener(); 191 | store.commit("increment"); 192 | expect(count).toEqual(1); 193 | }); 194 | 195 | test("Throw error if you try to listen with something other than a function", (t) => { 196 | let store = getNewStore(); 197 | expect(() => store.on("set", "hello")).toThrowError( 198 | "You need to provide a valid function as listener." 199 | ); 200 | }); 201 | 202 | test("Throw error if you try to remove a listener that is something other than a function", (t) => { 203 | let store = getNewStore(); 204 | expect(() => store.off("set", "hello")).toThrowError( 205 | "You need to provide a valid function as listener." 206 | ); 207 | }); 208 | 209 | test("Fail silently if you try to remove an already removed or not added listener", (t) => { 210 | let store = getNewStore(); 211 | store.off("set", () => {}); 212 | }); 213 | 214 | test("Add a listener only once", (t) => { 215 | let store = getNewStore(); 216 | let count = 0; 217 | let method = () => count++; 218 | expect(count).toEqual(0); 219 | store.on("set", method); 220 | store.on("set", method); 221 | store.on("set", method); 222 | store.commit("increment"); 223 | expect(count).toEqual(1); 224 | }); 225 | 226 | test("Test all listeners", async (t) => { 227 | let store = getNewStore(); 228 | let events = [ 229 | "addlistener", 230 | "removelistener", 231 | "set", 232 | "delete", 233 | "beforecommit", 234 | "commit", 235 | "beforedispatch", 236 | "dispatch", 237 | "getter", 238 | "plugin", 239 | "registerModule", 240 | "unregisterModule" 241 | ]; 242 | let methods = events.reduce((methods, event) => { 243 | methods[event] = { 244 | params: [], 245 | method: (...params) => methods[event].params.push(params) 246 | }; 247 | return methods; 248 | }, {}); 249 | 250 | events.forEach((event) => store.on(event, methods[event].method)); 251 | store.getters.length; 252 | await store.dispatch("pushB", 1); 253 | store.commit("deleteB"); 254 | let plugin = () => {}; 255 | store.use(plugin); 256 | let module = {}; 257 | store.registerModule("b", module); 258 | store.unregisterModule("b"); 259 | events.forEach((event) => store.off(event, methods[event].method)); 260 | 261 | // Add listener event 262 | expect(methods.addlistener.params.length).toEqual(events.length); 263 | expect(methods.addlistener.params).toEqual( 264 | events.map((event) => { 265 | return [ 266 | expect.any(Dragonbinder), // The store 267 | event, // The listener name 268 | methods[event].method // The listener added 269 | ]; 270 | }) 271 | ); 272 | 273 | // Remove listener event 274 | expect(methods.removelistener.params.length).toEqual(1); 275 | expect(methods.removelistener.params[0]).toEqual([ 276 | expect.any(Dragonbinder), // The store 277 | "addlistener", // The listener name 278 | methods.addlistener.method // The listener added 279 | ]); 280 | 281 | // Set event also triggered when you register a module 282 | expect(methods.set.params.length).toEqual(2); 283 | expect(methods.set.params[0]).toEqual([ 284 | expect.any(Dragonbinder), // The store 285 | "b", // The property name 286 | [1, 1], // The new value, 287 | [1] // The old value 288 | ]); 289 | 290 | // Delete event also triggered when you unregister a module 291 | expect(methods.delete.params.length).toEqual(2); 292 | expect(methods.delete.params[0]).toEqual([ 293 | expect.any(Dragonbinder), // The store 294 | "b", // The property name 295 | [1, 1] // The old value 296 | ]); 297 | 298 | // Before commit event 299 | expect(methods.beforecommit.params.length).toEqual(2); 300 | expect(methods.beforecommit.params[0]).toEqual([ 301 | expect.any(Dragonbinder), // The store 302 | "pushB", // The mutation name 303 | 1 // The params passed to the mutation 304 | ]); 305 | 306 | // Commit event 307 | expect(methods.commit.params.length).toEqual(2); 308 | expect(methods.commit.params[0]).toEqual([ 309 | expect.any(Dragonbinder), // The store 310 | "pushB", // The mutation name 311 | 1 // The params passed to the mutation 312 | ]); 313 | 314 | // Before dispatch event, 315 | expect(methods.beforedispatch.params.length).toEqual(1); 316 | expect(methods.beforedispatch.params[0]).toEqual([ 317 | expect.any(Dragonbinder), // The store 318 | "pushB", // The action name 319 | 1 // The params passed to the mutation 320 | ]); 321 | 322 | // Dispatch event 323 | expect(methods.dispatch.params.length).toEqual(1); 324 | expect(methods.dispatch.params[0]).toEqual([ 325 | expect.any(Dragonbinder), // The store 326 | "pushB", // The action name 327 | 1 // The params passed to the mutation 328 | ]); 329 | 330 | // Getter event 331 | expect(methods.getter.params.length).toEqual(2); 332 | expect(methods.getter.params[0]).toEqual([ 333 | expect.any(Dragonbinder), // The store 334 | "items", // The getter name 335 | [1] // The value of the getter 336 | ]); 337 | 338 | // Add plugin event 339 | expect(methods.plugin.params.length).toEqual(1); 340 | expect(methods.plugin.params[0]).toEqual([ 341 | expect.any(Dragonbinder), // The store 342 | plugin // The plugin added 343 | ]); 344 | 345 | // Register module event 346 | expect(methods.registerModule.params.length).toEqual(1); 347 | expect(methods.registerModule.params[0]).toEqual([ 348 | expect.any(Dragonbinder), // The store 349 | "b", // The namespace registered 350 | module, // The module definition 351 | expect.any(Dragonbinder) // The store created with the definition 352 | ]); 353 | 354 | // Unregister module event 355 | expect(methods.unregisterModule.params.length).toEqual(1); 356 | expect(methods.unregisterModule.params[0]).toEqual([ 357 | expect.any(Dragonbinder), // The store 358 | "b", // The namespace unregistered 359 | expect.any(Dragonbinder) // The store created with the definition 360 | ]); 361 | }); 362 | 363 | test("Register nested modules", () => { 364 | let store = getNewStore(); 365 | store.registerModule("my.module", { 366 | state: { hello: "world" }, 367 | mutations: { 368 | change(state) { 369 | state.hello = "mundo"; 370 | } 371 | } 372 | }); 373 | 374 | expect(store.init.modules).toEqual({ 375 | "my.module": expect.any(Dragonbinder) 376 | }); 377 | expect(store.state).toEqual( 378 | expect.objectContaining({ 379 | "my.module": { 380 | hello: "world" 381 | } 382 | }) 383 | ); 384 | }); 385 | 386 | test("Throw an error if you want to overwrite a registered module", () => { 387 | let store = getNewStore(); 388 | let myModule = { 389 | state: { hello: "world" }, 390 | mutations: { 391 | change(state) { 392 | state.hello = "mundo"; 393 | } 394 | } 395 | }; 396 | 397 | store.registerModule("my.module", myModule); 398 | 399 | expect(() => store.registerModule("my.module", myModule)).toThrowError( 400 | 'A module with the namespace "my.module" is already registered.' 401 | ); 402 | }); 403 | 404 | test("Unregister nested modules", () => { 405 | let store = getNewStore(); 406 | store.registerModule("my.module", { 407 | state: { hello: "world" }, 408 | mutations: { 409 | change(state) { 410 | state.hello = "mundo"; 411 | } 412 | } 413 | }); 414 | 415 | expect(store.init.modules).toEqual({ 416 | "my.module": expect.any(Dragonbinder) 417 | }); 418 | expect(store.state).toEqual( 419 | expect.objectContaining({ 420 | "my.module": { 421 | hello: "world" 422 | } 423 | }) 424 | ); 425 | 426 | store.unregisterModule("my.module"); 427 | 428 | expect(store.init.modules).toEqual({}); 429 | expect(store.state["my.module"]).toBeUndefined(); 430 | }); 431 | 432 | test("Fail silently if you try to unregister a nested module twice", () => { 433 | let store = getNewStore(); 434 | store.registerModule("my.module", { 435 | state: { hello: "world" }, 436 | mutations: { 437 | change(state) { 438 | state.hello = "mundo"; 439 | } 440 | } 441 | }); 442 | 443 | expect(store.init.modules).toEqual({ 444 | "my.module": expect.any(Dragonbinder) 445 | }); 446 | expect(store.state).toEqual( 447 | expect.objectContaining({ 448 | "my.module": { 449 | hello: "world" 450 | } 451 | }) 452 | ); 453 | 454 | store.unregisterModule("my.module"); 455 | store.unregisterModule("my.module"); 456 | 457 | expect(store.init.modules).toEqual({}); 458 | expect(store.state["my.module"]).toBeUndefined(); 459 | }); 460 | 461 | test("Call a nested module mutation", () => { 462 | let store = getNewStore(); 463 | store.registerModule("my.module", { 464 | state: { hello: "world" }, 465 | mutations: { 466 | change(state) { 467 | state.hello = "mundo"; 468 | } 469 | } 470 | }); 471 | 472 | expect(store.state).toEqual( 473 | expect.objectContaining({ 474 | "my.module": { 475 | hello: "world" 476 | } 477 | }) 478 | ); 479 | 480 | store.commit("my.module.change"); 481 | 482 | expect(store.state).toEqual( 483 | expect.objectContaining({ 484 | "my.module": { 485 | hello: "mundo" 486 | } 487 | }) 488 | ); 489 | }); 490 | 491 | test("Throw error if try to call a mutation of a non existent module", () => { 492 | let store = getNewStore(); 493 | store.registerModule("my.module", { 494 | state: { hello: "world" }, 495 | mutations: { 496 | change(state) { 497 | state.hello = "mundo"; 498 | } 499 | } 500 | }); 501 | 502 | expect(() => store.commit("my.module.not.change")).toThrowError( 503 | 'The module "my.module.not" does not exists.' 504 | ); 505 | }); 506 | 507 | test("Throw error if mutation of a nested module does not exists", () => { 508 | let store = getNewStore(); 509 | store.registerModule("my.module", { 510 | state: { hello: "world" }, 511 | mutations: { 512 | change(state) { 513 | state.hello = "mundo"; 514 | } 515 | } 516 | }); 517 | 518 | expect(() => store.commit("my.module.not-change")).toThrowError( 519 | 'The mutation "not-change" does not exists.' 520 | ); 521 | }); 522 | 523 | test("Call a nested module action", async () => { 524 | let store = getNewStore(); 525 | store.registerModule("my.module", { 526 | state: { hello: "world" }, 527 | mutations: { 528 | change(state) { 529 | state.hello = "mundo"; 530 | } 531 | }, 532 | actions: { 533 | change(store) { 534 | store.commit("change"); 535 | } 536 | } 537 | }); 538 | 539 | expect(store.state).toEqual( 540 | expect.objectContaining({ 541 | "my.module": { 542 | hello: "world" 543 | } 544 | }) 545 | ); 546 | 547 | await store.dispatch("my.module.change"); 548 | 549 | expect(store.state).toEqual( 550 | expect.objectContaining({ 551 | "my.module": { 552 | hello: "mundo" 553 | } 554 | }) 555 | ); 556 | }); 557 | 558 | test("Call a root action and mutation from a nested module action", async () => { 559 | let store = getNewStore(); 560 | store.registerModule("my.module", { 561 | actions: { 562 | async change(store, payload) { 563 | store.rootStore.commit("increment", payload); 564 | await store.rootStore.dispatch("pushB", payload); 565 | } 566 | } 567 | }); 568 | 569 | expect(store.state).toEqual( 570 | expect.objectContaining({ 571 | a: 0, 572 | b: [1] 573 | }) 574 | ); 575 | 576 | await store.dispatch("my.module.change", 5); 577 | 578 | expect(store.state).toEqual( 579 | expect.objectContaining({ 580 | a: 5, 581 | b: [1, 5] 582 | }) 583 | ); 584 | }); 585 | 586 | test("Throw error if try to call a action of a non existent module", () => { 587 | let store = getNewStore(); 588 | store.registerModule("my.module", { 589 | state: { hello: "world" }, 590 | mutations: { 591 | change(state) { 592 | state.hello = "mundo"; 593 | } 594 | }, 595 | actions: { 596 | change(store) { 597 | store.commit("change"); 598 | } 599 | } 600 | }); 601 | 602 | expect(() => store.dispatch("my.module.not.change")).toThrowError( 603 | 'The module "my.module.not" does not exists.' 604 | ); 605 | }); 606 | 607 | test("Throw error if action of a nested module does not exists", () => { 608 | let store = getNewStore(); 609 | store.registerModule("my.module", { 610 | state: { hello: "world" }, 611 | mutations: { 612 | change(state) { 613 | state.hello = "mundo"; 614 | } 615 | }, 616 | actions: { 617 | change(store) { 618 | store.commit("change"); 619 | } 620 | } 621 | }); 622 | 623 | expect(() => store.dispatch("my.module.not-change")).toThrowError( 624 | 'The action "not-change" does not exists.' 625 | ); 626 | }); 627 | 628 | test("Use a getter to obtain a property from the nested module state", (t) => { 629 | let store = getNewStore(); 630 | store.registerModule("my.module", { 631 | state: { hello: "world" }, 632 | mutations: { 633 | change(state) { 634 | state.hello = "mundo"; 635 | } 636 | }, 637 | getters: { 638 | entity(state, getters) { 639 | return state.hello; 640 | }, 641 | capitalizedEntity(state, getters) { 642 | return getters.entity.charAt(0).toUpperCase() + getters.entity.slice(1); 643 | } 644 | } 645 | }); 646 | expect(store.getters["my.module.entity"]).toEqual("world"); 647 | }); 648 | 649 | test("Use a nested module getter to obtain data using another nested getter", (t) => { 650 | let store = getNewStore(); 651 | store.registerModule("my.module", { 652 | state: { hello: "world" }, 653 | mutations: { 654 | change(state) { 655 | state.hello = "mundo"; 656 | } 657 | }, 658 | getters: { 659 | entity(state, getters) { 660 | return state.hello; 661 | }, 662 | capitalizedEntity(state, getters) { 663 | return getters.entity.charAt(0).toUpperCase() + getters.entity.slice(1); 664 | } 665 | } 666 | }); 667 | expect(store.getters["my.module.capitalizedEntity"]).toEqual("World"); 668 | }); 669 | 670 | test("Fail silently if you try to get an undefined nested module getter, getter must return undefined", (t) => { 671 | let store = getNewStore(); 672 | store.registerModule("my.module", { 673 | state: { hello: "world" }, 674 | mutations: { 675 | change(state) { 676 | state.hello = "mundo"; 677 | } 678 | }, 679 | getters: { 680 | entity(state, getters) { 681 | return state.hello; 682 | }, 683 | capitalizedEntity(state, getters) { 684 | return getters.entity.charAt(0).toUpperCase() + getters.entity.slice(1); 685 | } 686 | } 687 | }); 688 | expect(store.getters["my.module.capitalized"]).toBeUndefined(); 689 | }); 690 | 691 | test("Fail silently if you try to get a getter of a non existent nested module", (t) => { 692 | let store = getNewStore(); 693 | store.registerModule("my.module", { 694 | state: { hello: "world" }, 695 | mutations: { 696 | change(state) { 697 | state.hello = "mundo"; 698 | } 699 | }, 700 | getters: { 701 | entity(state, getters) { 702 | return state.hello; 703 | }, 704 | capitalizedEntity(state, getters) { 705 | return getters.entity.charAt(0).toUpperCase() + getters.entity.slice(1); 706 | } 707 | } 708 | }); 709 | expect(store.getters["my.capitalized"]).toBeUndefined(); 710 | expect(store.getters["other.capitalized"]).toBeUndefined(); 711 | }); 712 | 713 | test("Use a getter to obtain a property from the root state and root getters", (t) => { 714 | let store = getNewStore(); 715 | store.registerModule("my.module", { 716 | state: { hello: "world" }, 717 | mutations: { 718 | change(state) { 719 | state.hello = "mundo"; 720 | } 721 | }, 722 | getters: { 723 | length(state, getters, rootState, rootGetters) { 724 | return rootGetters.items.length; 725 | }, 726 | items(state, getters, rootState, rootGetters) { 727 | return rootState.b; 728 | } 729 | } 730 | }); 731 | expect(store.getters["my.module.length"]).toEqual(1); 732 | expect(store.getters["my.module.items"]).toEqual([1]); 733 | }); 734 | 735 | test("Register nested modules with initial modules property", () => { 736 | let store = new Dragonbinder({ 737 | modules: { 738 | "my.module": { 739 | state: { hello: "world" }, 740 | mutations: { 741 | change(state) { 742 | state.hello = "mundo"; 743 | } 744 | } 745 | } 746 | } 747 | }); 748 | 749 | expect(store.init.modules).toEqual({ 750 | "my.module": expect.any(Dragonbinder) 751 | }); 752 | expect(store.state).toEqual( 753 | expect.objectContaining({ 754 | "my.module": { 755 | hello: "world" 756 | } 757 | }) 758 | ); 759 | }); 760 | 761 | test("Register nested modules with initial modules property and with child nested modules", () => { 762 | let moduleA = { 763 | state: { hello: "world" }, 764 | mutations: { 765 | change(state) { 766 | state.hello = "mundo"; 767 | } 768 | } 769 | }; 770 | 771 | let moduleB = { 772 | state: { hola: "mundo" }, 773 | mutations: { 774 | change(state) { 775 | state.hola = "world"; 776 | } 777 | }, 778 | modules: { 779 | a: moduleA 780 | } 781 | }; 782 | 783 | let store = new Dragonbinder({ 784 | modules: { 785 | b: moduleB 786 | } 787 | }); 788 | 789 | expect(store.init.modules).toEqual({ 790 | b: expect.any(Dragonbinder), 791 | "b.a": expect.any(Dragonbinder) 792 | }); 793 | expect(store.state).toEqual( 794 | expect.objectContaining({ 795 | b: { 796 | hola: "mundo" 797 | }, 798 | "b.a": { 799 | hello: "world" 800 | } 801 | }) 802 | ); 803 | }); 804 | 805 | test("Trigger listener updating a nested module store", (t) => { 806 | let store = getNewStore(); 807 | store.registerModule("my.module", { 808 | state: { hello: "world" }, 809 | mutations: { 810 | change(state) { 811 | state.hello = "mundo"; 812 | }, 813 | deleteHello(state) { 814 | delete state.hello; 815 | } 816 | } 817 | }); 818 | let count = 0; 819 | let method = () => count++; 820 | expect(count).toEqual(0); 821 | store.on("set", method); 822 | store.on("delete", method); 823 | store.commit("my.module.change"); 824 | store.commit("my.module.deleteHello"); 825 | expect(count).toEqual(2); 826 | }); 827 | 828 | test("Trigger listeners within a child nested module", (t) => { 829 | let moduleA = { 830 | state: { hello: "world" }, 831 | mutations: { 832 | change(state) { 833 | state.hello = "mundo"; 834 | } 835 | } 836 | }; 837 | 838 | let moduleB = { 839 | state: { hola: "mundo" }, 840 | mutations: { 841 | change(state) { 842 | state.hola = "world"; 843 | } 844 | }, 845 | modules: { 846 | a: moduleA 847 | } 848 | }; 849 | 850 | let store = new Dragonbinder({ 851 | modules: { 852 | "my.module": moduleB 853 | } 854 | }); 855 | 856 | let count = 0; 857 | let method = () => count++; 858 | expect(count).toEqual(0); 859 | store.on("set", method); 860 | store.commit("my.module.change"); 861 | store.commit("my.module.a.change"); 862 | expect(count).toEqual(2); 863 | }); 864 | 865 | test("Declare state as factory function", (t) => { 866 | const myModule = { 867 | state() { 868 | return { 869 | count: 1 870 | }; 871 | }, 872 | mutations: { 873 | increment(state, payload) { 874 | state.count = state.count + payload; 875 | } 876 | } 877 | }; 878 | 879 | const store1 = new Dragonbinder(myModule); 880 | const store2 = new Dragonbinder(myModule); 881 | 882 | store1.commit("increment", 5); 883 | store2.commit("increment", 3); 884 | 885 | expect(store1.state.count).toEqual(6); 886 | expect(store2.state.count).toEqual(4); 887 | }); 888 | 889 | test("Register nested modules with child nested module property", () => { 890 | let store = getNewStore(); 891 | store.registerModule("my.module", { 892 | state: { hello: "world" }, 893 | mutations: { 894 | change(state) { 895 | state.hello = "mundo"; 896 | } 897 | }, 898 | modules: { 899 | child: { 900 | state: { 901 | count: 1 902 | } 903 | } 904 | } 905 | }); 906 | 907 | expect(store.init.modules).toEqual({ 908 | "my.module": expect.any(Dragonbinder), 909 | "my.module.child": expect.any(Dragonbinder) 910 | }); 911 | expect(store.state).toEqual( 912 | expect.objectContaining({ 913 | "my.module": { 914 | hello: "world" 915 | }, 916 | "my.module.child": { 917 | count: 1 918 | } 919 | }) 920 | ); 921 | }); 922 | 923 | test("Unregister nested modules with child nested module", () => { 924 | let store = getNewStore(); 925 | store.registerModule("my.module", { 926 | state: { hello: "world" }, 927 | mutations: { 928 | change(state) { 929 | state.hello = "mundo"; 930 | } 931 | }, 932 | modules: { 933 | child: { 934 | state: { 935 | count: 1 936 | } 937 | } 938 | } 939 | }); 940 | 941 | expect(store.init.modules).toEqual({ 942 | "my.module": expect.any(Dragonbinder), 943 | "my.module.child": expect.any(Dragonbinder) 944 | }); 945 | expect(store.state).toEqual( 946 | expect.objectContaining({ 947 | "my.module": { 948 | hello: "world" 949 | }, 950 | "my.module.child": { 951 | count: 1 952 | } 953 | }) 954 | ); 955 | 956 | store.unregisterModule("my.module"); 957 | 958 | expect(store.init.modules).toEqual({}); 959 | expect(store.state["my.module"]).toBeUndefined(); 960 | expect(store.state["my.module.child"]).toBeUndefined(); 961 | }); 962 | -------------------------------------------------------------------------------- /tests/test.cjs: -------------------------------------------------------------------------------- 1 | const Dragonbinder = require("../dist/dragonbinder.js"); 2 | const { expect } = require("expect"); 3 | 4 | const dragonbinder = new Dragonbinder({ 5 | state: { b: [1], a: 0, c: { a: 1 } }, 6 | mutations: { 7 | pushB(state, payload) { 8 | state.b = [...state.b, payload]; 9 | }, 10 | deleteB(state) { 11 | delete state.b; 12 | }, 13 | increment(state, payload) { 14 | state.a = state.a + payload; 15 | }, 16 | changeC(state, payload) { 17 | state.c = Object.assign({}, state.c, { a: payload }); 18 | }, 19 | changeCDirectly(state, payload) { 20 | state.c.a = payload; 21 | } 22 | }, 23 | actions: { 24 | pushB(context, payload) { 25 | return new Promise((resolve) => { 26 | setTimeout(() => { 27 | context.commit("pushB", payload); 28 | resolve(); 29 | }, 1000); 30 | }); 31 | } 32 | }, 33 | getters: { 34 | length(state, getters) { 35 | return getters.items.length; 36 | }, 37 | items(state, getters) { 38 | return state.b; 39 | } 40 | } 41 | }); 42 | 43 | expect(dragonbinder.state.a).toBe(0); 44 | dragonbinder.commit("increment", 1); 45 | expect(dragonbinder.state.a).toBe(1); 46 | 47 | expect(dragonbinder.getters.length).toBe(1); 48 | dragonbinder.commit("pushB", 2); 49 | expect(dragonbinder.getters.length).toBe(2); 50 | -------------------------------------------------------------------------------- /tests/test.min.js: -------------------------------------------------------------------------------- 1 | const Dragonbinder = require("../dist/dragonbinder.min.js"); 2 | const { expect } = require("expect"); 3 | 4 | const dragonbinder = new Dragonbinder({ 5 | state: { b: [1], a: 0, c: { a: 1 } }, 6 | mutations: { 7 | pushB(state, payload) { 8 | state.b = [...state.b, payload]; 9 | }, 10 | deleteB(state) { 11 | delete state.b; 12 | }, 13 | increment(state, payload) { 14 | state.a = state.a + payload; 15 | }, 16 | changeC(state, payload) { 17 | state.c = Object.assign({}, state.c, { a: payload }); 18 | }, 19 | changeCDirectly(state, payload) { 20 | state.c.a = payload; 21 | } 22 | }, 23 | actions: { 24 | pushB(context, payload) { 25 | return new Promise((resolve) => { 26 | setTimeout(() => { 27 | context.commit("pushB", payload); 28 | resolve(); 29 | }, 1000); 30 | }); 31 | } 32 | }, 33 | getters: { 34 | length(state, getters) { 35 | return getters.items.length; 36 | }, 37 | items(state, getters) { 38 | return state.b; 39 | } 40 | } 41 | }); 42 | 43 | expect(dragonbinder.state.a).toBe(0); 44 | dragonbinder.commit("increment", 1); 45 | expect(dragonbinder.state.a).toBe(1); 46 | 47 | expect(dragonbinder.getters.length).toBe(1); 48 | dragonbinder.commit("pushB", 2); 49 | expect(dragonbinder.getters.length).toBe(2); 50 | -------------------------------------------------------------------------------- /tests/test.mjs: -------------------------------------------------------------------------------- 1 | import Dragonbinder from "../dist/dragonbinder.mjs"; 2 | import { expect } from "expect"; 3 | 4 | const dragonbinder = new Dragonbinder({ 5 | state: { b: [1], a: 0, c: { a: 1 } }, 6 | mutations: { 7 | pushB(state, payload) { 8 | state.b = [...state.b, payload]; 9 | }, 10 | deleteB(state) { 11 | delete state.b; 12 | }, 13 | increment(state, payload) { 14 | state.a = state.a + payload; 15 | }, 16 | changeC(state, payload) { 17 | state.c = Object.assign({}, state.c, { a: payload }); 18 | }, 19 | changeCDirectly(state, payload) { 20 | state.c.a = payload; 21 | } 22 | }, 23 | actions: { 24 | pushB(context, payload) { 25 | return new Promise((resolve) => { 26 | setTimeout(() => { 27 | context.commit("pushB", payload); 28 | resolve(); 29 | }, 1000); 30 | }); 31 | } 32 | }, 33 | getters: { 34 | length(state, getters) { 35 | return getters.items.length; 36 | }, 37 | items(state, getters) { 38 | return state.b; 39 | } 40 | } 41 | }); 42 | 43 | expect(dragonbinder.state.a).toBe(0); 44 | dragonbinder.commit("increment", 1); 45 | expect(dragonbinder.state.a).toBe(1); 46 | 47 | expect(dragonbinder.getters.length).toBe(1); 48 | dragonbinder.commit("pushB", 2); 49 | expect(dragonbinder.getters.length).toBe(2); 50 | --------------------------------------------------------------------------------