├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom-issue.md │ └── other-issue.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── build ├── banner.txt └── rollup.js ├── demo ├── README.md ├── index.html └── package.json ├── docs ├── .nojekyll ├── assets │ ├── css │ │ └── styles.css │ ├── img │ │ ├── demo │ │ │ ├── demo-code.png │ │ │ ├── demo-feature.png │ │ │ ├── demo-help.png │ │ │ └── demo-simple.png │ │ ├── logos │ │ │ ├── logo.png │ │ │ ├── logo.svg │ │ │ ├── splash-landscape.png │ │ │ └── splash-square.png │ │ └── readme │ │ │ ├── pathify-diagram.png │ │ │ ├── pathify-path.png │ │ │ └── splash-github.png │ ├── js │ │ └── plugins.js │ ├── scss │ │ ├── partials │ │ │ ├── _code.scss │ │ │ ├── _cover.scss │ │ │ ├── _formatting.scss │ │ │ ├── _plugins.scss │ │ │ ├── _sidebar.scss │ │ │ └── _variables.scss │ │ └── styles.scss │ └── vendor │ │ ├── docsify │ │ ├── docsify.min.js │ │ ├── merriweather.css │ │ ├── search.min.js │ │ └── vue.css │ │ ├── ga.min.js │ │ ├── prism │ │ ├── prism-pathify.js │ │ └── prism.js │ │ └── vue │ │ └── vue.min.js ├── index.html └── pages │ ├── cover.md │ ├── discussion │ ├── faq.md │ └── rationale.md │ ├── guide │ ├── accessors.md │ ├── component.md │ ├── decorators.md │ ├── paths.md │ ├── properties.md │ └── store.md │ ├── home.md │ ├── intro │ ├── demos.md │ └── pathify.md │ ├── reference │ ├── api.md │ ├── code.md │ └── products.js │ ├── setup │ ├── config.md │ ├── install.md │ ├── mapping.md │ └── options.md │ └── sidebar.md ├── package-lock.json ├── package.json ├── src ├── classes │ └── Payload.js ├── helpers │ ├── accessors.js │ ├── component.js │ ├── decorators.js │ ├── modules.js │ ├── store.js │ └── vuex.js ├── main.js ├── plugin │ ├── debug.js │ ├── options.js │ └── pathify.js ├── services │ ├── formatters.js │ ├── paths.js │ ├── resolver.js │ ├── store.js │ └── wildcards.js └── utils │ └── object.js ├── tests ├── helpers │ ├── index.js │ └── pathify.js ├── store-accessors.test.js └── store-helpers.test.js └── types ├── README.md ├── index.d.ts ├── test ├── index.ts └── tsconfig.json ├── tsconfig.json ├── vue.d.ts └── vuex.d.ts /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something not working as it should 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 | **Expected behaviour** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Additional context** 17 | Add any other context about the problem here. 18 | 19 | **Code example** 20 | If you need to demo code, fork and edit the Pathify simple demo... 21 | 22 | - https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/simple 23 | 24 | ...then paste a link to your edited demo. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Add a bug report or feature request here. 11 | 12 | If you need to demo code, fork and edit the Pathify simple demo... 13 | 14 | - https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/simple 15 | 16 | ...then paste a link to your edited demo. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issue 3 | about: Need help, have a feature request, etc... 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Add any details here. 11 | 12 | If you need to demo code, fork and edit the Pathify simple demo... 13 | 14 | - https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/simple 15 | 16 | ...then paste a link to your edited demo. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | dist/ 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.5.1] - 2021-08-04 9 | ### Fixed 10 | - Optimise serialization code 11 | 12 | ## [1.5.0] - 2021-08-03 13 | ### Added 14 | - Support for serialized payloads - #125 / @Heziode 15 | 16 | ## [1.4.5] - 2020-12-17 17 | ### Fixed 18 | - Setting values on objects using numeric keys is now supported 19 | - Setting deep properties using numeric keys now correctly creates arrays 20 | 21 | ## [1.4.4] - 2020-12-02 22 | ### Fixed 23 | - Fix Payload type - #101 / @VesterDe 24 | 25 | ## [1.4.3] - 2020-12-01 26 | ### Fixed 27 | - Update dependencies 28 | 29 | ## [1.4.2] - 2020-12-01 30 | ### Fixed 31 | - Fix broken UMD bundles - #117 / @johannes-z 32 | 33 | ## [1.4.1] - 2019-12-16 34 | ### Fixed 35 | - IE11 compatibility - #77 / @JessicaSachs 36 | 37 | ## [1.4.0] - 2019-09-08 38 | ### Added 39 | - Enable variable expansion in component helper paths 40 | 41 | ## [1.3.0] - 2019-09-08 42 | ### Added 43 | - Enable array read / write for sub-properties 44 | 45 | ## [1.2.5] - 2019-09-07 46 | ### Fixed 47 | - Fixed issue where get and set helpers cached reference to store 48 | 49 | ## [1.2.3] - 2019-06-13 50 | ### Added 51 | - TypeScript: added overload for array and object accessors 52 | 53 | ### Changed 54 | - Removed dependency on Lodash `deepClone()` for a naive clone method 55 | 56 | ### Fixes 57 | - TypeScript: added proper generics to accessors - #58 / @KaelWD 58 | 59 | ## [1.2.2] - 2019-03-11 60 | ### Added 61 | - Added Class Component to dependencies 62 | 63 | ## [1.2.0] - 2019-03-11 64 | ### Added 65 | - Typescript typings file - #38 / @ozum 66 | - Add TypeScript property decorators and docs - #38 / @ozum 67 | 68 | ### Fixed 69 | - Use typeof instead of instanceof to defend against Nuxt errors - #46 / @SebastienTainon 70 | - Added missing return to callOne() - #43 / @germanp 71 | - Allow dollar sign in getKeys - #41 / @germanp 72 | - Return the call to vuex.store.dispatch - #37 / @nchutchind 73 | 74 | ## [1.1.0] - 2018-06-11 75 | ### Added 76 | - Ability to create new sub-properties on the fly 77 | - `call()` helper to map actions using the same syntax as `get()` and `sync()` 78 | - `registerModule()` helper to register wildcard members for dynamic modules 79 | 80 | ### Changed 81 | - Wildcards can now appear anywhere in the last segment of a path 82 | - Wildcards targeting module properties must now be explicit, i.e. `foo/*` rather than `foo*` 83 | - `deep` option format is now 0: disabled, 1: read-write, 2: read-write-create 84 | - `deep` option can now be changed at any time 85 | - `Payload#update()` now **returns** the updated state, rather than updating the passed state 86 | 87 | ### Fixed 88 | - Invalid computed property paths now return empty functions 89 | - Bug in sync where invalid paths would cause error message to error 90 | 91 | ## [1.0.10] - 2018-05-20 92 | ### Fixed 93 | - Fixed invalid wildcard bug which caused Nuxt to bomb 94 | 95 | ## [1.0.9] - 2018-05-18 96 | ### Fixed 97 | - Fixed setting of sub-properties when using mutations **and** actions 98 | 99 | ## [1.0.8] - 2018-05-12 100 | ### Added 101 | - Banner to built files 102 | 103 | ### Changed 104 | - Unknown modules now throw error rather than log to console (for Nuxt compatibility) 105 | 106 | ### Fixed 107 | - `store.get()` regression fixed 108 | 109 | ## [1.0.7] - 2018-05-11 110 | ### Fixed 111 | - `sync()` now reads only from state and not getters 112 | 113 | ## [1.0.6] - 2018-05-11 114 | ### Fixed 115 | - Vuex getter functions now return as functions not values for computed properties 116 | 117 | ## [1.0.5] - 2018-05-09 118 | ### Added 119 | - State can now be passed to `make.*` as a function 120 | - Added CHANGELOG.md 121 | 122 | ### Changed 123 | - Removed `only` parameter from `make.*` helpers; function now takes single `state` / `keys` parameter 124 | - Updated docs accordingly 125 | 126 | ## [1.0.4] - 2018-05-04 127 | ### Removed 128 | - Removed component helpers set() function 129 | 130 | ## [1.0.3] - 2018-05-04 131 | ### Fixed 132 | - Fixed bug with module-level wildcard get() not returning getters 133 | 134 | ## [1.0.2] - 2018-05-03 135 | ### Fixed 136 | - Fixed error message displaying original package name 137 | 138 | ## [1.0.1] - 2018-04-30 139 | ### Changed 140 | - Docs tidy-ups and clarifications 141 | 142 | ## [1.0.0] - 2018-04-23 143 | - Official release 144 | 145 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Overview 4 | 5 | PRs are welcome! 6 | 7 | It's the usual approach: 8 | 9 | - Discuss in an issue 10 | - Branch 11 | - Push 12 | - PR 13 | 14 | See notes [here](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/). 15 | 16 | 17 | ## General help / issues 18 | 19 | Feel free to open issues for bugs. 20 | 21 | For help, open an issue or catch me on the [Vueland Vuex channel](https://discordapp.com/channels/325477692906536972/325479491453583372). 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave Stewart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![splash](https://raw.githubusercontent.com/davestewart/vuex-pathify/master/docs/assets/img/readme/splash-github.png) 3 | 4 | ## Overview 5 | 6 | Pathify makes working with Vuex **easy**, with a **declarative**, **state-based**, **path syntax**: 7 | 8 | ![pathify-diagram](https://raw.githubusercontent.com/davestewart/vuex-pathify/master/docs/assets/img/readme/pathify-path.png) 9 | 10 | 11 | Paths can reference any **module**, **property** or **sub-property**: 12 | 13 | ![pathify-diagram](https://raw.githubusercontent.com/davestewart/vuex-pathify/master/docs/assets/img/readme/pathify-diagram.png) 14 | 15 | Pathify's aim is to simplify the overall Vuex development experience by abstracting away Vuex's complex setup and reliance on manually-written code. The path syntax does the heavy-lifting, with a small set of helper functions used to directly access or wire up components to the store. 16 | 17 | ## Examples 18 | 19 | **Get** or **set** data without **syntax juggling** or worrying about **implementation**: 20 | 21 | ```js 22 | store.get('loaded') 23 | store.set('loaded', true) 24 | ``` 25 | 26 | Reach into **sub-properties** and **arrays**: 27 | 28 | ```js 29 | store.get('products@items.0.name') 30 | store.set('products@items.1.name', 'Vuex Pathify') 31 | ``` 32 | 33 | Set up **one or two-way** data binding on **any** store value without **bloat** or **fuss**: 34 | 35 | ```js 36 | computed: { 37 | products: get('products'), 38 | category: sync('filters@category') 39 | } 40 | ``` 41 | 42 | Wire **multiple** properties (or sub-properties) using **array**, **object** and **wildcard** formats: 43 | 44 | ```js 45 | computed: { 46 | ...sync('filters@sort', [ 47 | 'order', 48 | 'key' 49 | ]), 50 | 51 | ...sync('filters@sort', { 52 | sortOrder: 'order', 53 | sortKey: 'key' 54 | }), 55 | 56 | ...sync('filters@sort.*') 57 | } 58 | ``` 59 | 60 | Use **variable expansion** to dynamically reference store properties: 61 | 62 | ```js 63 | computed: { 64 | product: get('products@items:index') 65 | } 66 | ``` 67 | 68 | Set up mutations – **including sub-property mutations** – in a single line: 69 | 70 | ```js 71 | make.mutations(state) 72 | ``` 73 | 74 | ## Results 75 | 76 | In practical terms, Pathify results in: 77 | 78 | - less cognitive overhead 79 | - zero store boilerplate 80 | - one-liner wiring 81 | - cleaner code 82 | - lighter files 83 | 84 | The [code comparison](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=%23%2Fcode%2Flarge) demo demonstrates reductions in lines of code of between **2 and 14 times** (or more) depending on store size and setup. 85 | 86 | To see the principles behind such radical code reduction, check out the [Pathify 101](https://davestewart.github.io/vuex-pathify/#/intro/pathify). 87 | 88 | ## Next steps 89 | 90 | Get started: 91 | 92 | - [Installation](https://www.npmjs.com/package/vuex-pathify) 93 | - [Documentation](https://davestewart.github.io/vuex-pathify) 94 | 95 | Demos: 96 | 97 | - [Simple demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/simple) 98 | - [Main demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main) 99 | - [Nuxt demo](https://github.com/davestewart/vuex-pathify-demos/tree/master/nuxt) 100 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: "current" 8 | } 9 | } 10 | ] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /build/banner.txt: -------------------------------------------------------------------------------- 1 | Bundle of: <%= pkg.name %> 2 | Generated: <%= moment().format('YYYY-MM-DD') %> 3 | Version: <%= pkg.version %> 4 | -------------------------------------------------------------------------------- /build/rollup.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------ 2 | // setup 3 | // ------------------------------------------------------------------------------------------ 4 | 5 | import path from 'path' 6 | import license from 'rollup-plugin-license' 7 | import commonjs from 'rollup-plugin-commonjs' 8 | import uglify from 'rollup-plugin-uglify' 9 | import buble from 'rollup-plugin-buble' 10 | 11 | const pkg = require('../package.json') 12 | const external = Object.keys(pkg.dependencies || {}) 13 | const name = pkg.name 14 | const className = name.replace(/(^\w|-\w)/g, c => c.replace('-', '').toUpperCase()) 15 | 16 | function output (ext, format = 'umd') { 17 | return { 18 | name: className, 19 | file: `dist/${name}.${ext}`, 20 | format: format, 21 | // exports: 'default', 22 | } 23 | } 24 | 25 | // ------------------------------------------------------------------------------------------ 26 | // build 27 | // ------------------------------------------------------------------------------------------ 28 | 29 | const umd = { 30 | input: 'src/main.js', 31 | external: external, 32 | output: output('js'), 33 | plugins: [ 34 | license({ 35 | banner: { 36 | file: path.join(__dirname, 'banner.txt') 37 | }, 38 | }), 39 | commonjs(), 40 | buble() 41 | ] 42 | } 43 | 44 | const min = Object.assign({}, umd, { 45 | output: output('min.js'), 46 | plugins: [...umd.plugins, uglify()] 47 | }) 48 | 49 | const es = Object.assign({}, umd, { 50 | output: output('esm.js', 'es') 51 | }) 52 | 53 | export default [umd, min, es] 54 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Demos 2 | 3 | Pathify demos are available at: 4 | 5 | - https://github.com/davestewart/vuex-pathify-demos 6 | 7 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vuex Pathify - Demos 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 34 | 35 | 36 | 37 |
38 | 39 |

Vuex Pathify Demos

40 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-pathify-demos", 3 | "description": "A set of demos for the Vuex Pathify plugin", 4 | "author": "Dave Stewart", 5 | "main": "index.html" 6 | } 7 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/.nojekyll -------------------------------------------------------------------------------- /docs/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .content { 3 | padding-top: 30px; } 4 | 5 | .markdown-section { 6 | padding-bottom: 50vh; } 7 | 8 | .markdown-section h1, .markdown-section h2, .markdown-section h3, .markdown-section h4, .markdown-section h5 { 9 | font-family: Merriweather, serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | font-weight: 400; } 13 | .markdown-section h1 a, .markdown-section h2 a, .markdown-section h3 a, .markdown-section h4 a, .markdown-section h5 a { 14 | font-weight: 400; 15 | transition: 0.2s color; } 16 | .markdown-section h1 a.anchor:hover, .markdown-section h2 a.anchor:hover, .markdown-section h3 a.anchor:hover, .markdown-section h4 a.anchor:hover, .markdown-section h5 a.anchor:hover { 17 | text-decoration: none; } 18 | .markdown-section h1 a.anchor:hover span, .markdown-section h2 a.anchor:hover span, .markdown-section h3 a.anchor:hover span, .markdown-section h4 a.anchor:hover span, .markdown-section h5 a.anchor:hover span { 19 | color: #07a6a6; } 20 | 21 | .markdown-section h1, .markdown-section h2 { 22 | margin-left: -15px; 23 | padding-left: 15px; 24 | padding-bottom: .2em; } 25 | 26 | .markdown-section h2 { 27 | border-bottom: 1px solid #EEE; } 28 | 29 | .markdown-section h1 + h2, 30 | .markdown-section h1 + h3, 31 | .markdown-section h1 + h4, 32 | .markdown-section h1 + blockquote { 33 | margin-top: 0.75em; } 34 | 35 | .markdown-section h1 { 36 | font-size: 3em; } 37 | 38 | .markdown-section h2 { 39 | font-size: 2.2em; } 40 | 41 | .markdown-section h3 { 42 | font-size: 1.8em; } 43 | 44 | .markdown-section table, .markdown-section ul, .markdown-section ol { 45 | margin-left: 25px; } 46 | 47 | .markdown-section ol li p { 48 | margin-top: 0.5em; 49 | margin-bottom: 0.5em; } 50 | .markdown-section ol li p:last-child { 51 | margin-bottom: 2em; } 52 | 53 | .markdown-section .indent { 54 | margin-left: 20px; } 55 | 56 | @media only screen and (max-width: 600px) { 57 | .markdown-section pre { 58 | margin-left: 0; } } 59 | 60 | .markdown-section p.tip + p.tip { 61 | margin-top: -1.5em; } 62 | 63 | .markdown-section p a:hover { 64 | color: #09d7d7; } 65 | 66 | .markdown-section em { 67 | color: #34495e; } 68 | 69 | .markdown-section pre { 70 | padding: 0; 71 | margin-left: 25px; } 72 | .markdown-section pre > code { 73 | padding: 10px 15px; } 74 | .markdown-section pre + pre { 75 | margin-top: -.75em; } 76 | .markdown-section pre:after { 77 | padding: 8px 10px; } 78 | 79 | .markdown-section code, .markdown-section pre { 80 | border-radius: 4px; 81 | color: #ff7337; } 82 | 83 | .markdown-section h2 code, .markdown-section h3 code, .markdown-section h4 code { 84 | font-size: 1em; 85 | font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; 86 | font-weight: 700; 87 | display: inline-block; 88 | background: none; 89 | padding: 0; 90 | margin: 0; 91 | color: inherit; } 92 | .markdown-section h2 code:before, .markdown-section h3 code:before, .markdown-section h4 code:before { 93 | display: inline-block; 94 | width: 1em; 95 | margin-left: -1em; 96 | content: "#"; 97 | color: #42b983; } 98 | 99 | .markdown-section pre[data-lang="pathify"], 100 | .markdown-section code.lang-pathify { 101 | margin: 0; 102 | overflow: visible; 103 | background: none; } 104 | 105 | .markdown-section pre[data-lang="pathify"] { 106 | padding: 2em 2em 4em; 107 | border-radius: 0; } 108 | @media screen and (max-width: 768px) { 109 | .markdown-section pre[data-lang="pathify"] { 110 | padding: 1em 1em 1.5em; } } 111 | .markdown-section pre[data-lang="pathify"]:after { 112 | content: none; } 113 | .markdown-section pre[data-lang="pathify"]:nth-of-type(1) { 114 | text-align: center !important; } 115 | .markdown-section pre[data-lang="pathify"]:nth-of-type(1) code { 116 | letter-spacing: 0.01em; 117 | font-size: 3.3em; } 118 | @media screen and (max-width: 768px) { 119 | .markdown-section pre[data-lang="pathify"]:nth-of-type(1) code { 120 | font-size: 8vw; } } 121 | .markdown-section pre[data-lang="pathify"] + pre[data-lang="pathify"] { 122 | padding-top: 0; 123 | margin-top: -3em; } 124 | @media screen and (max-width: 768px) { 125 | .markdown-section pre[data-lang="pathify"] + pre[data-lang="pathify"] { 126 | margin-top: -1em; } } 127 | 128 | .markdown-section .token.keyword { 129 | color: #ff7337; } 130 | 131 | .markdown-section code.lang-pathify, 132 | .markdown-section code.lang-pathify * { 133 | -webkit-font-smoothing: antialiased; } 134 | 135 | .markdown-section code.lang-pathify { 136 | line-height: 1.1; 137 | letter-spacing: -0.03em; 138 | color: #203245; 139 | text-align: left; 140 | display: inline-block; 141 | font-family: Baloo, Merriweather, Serif; 142 | font-size: 2.2rem; 143 | font-weight: 300; 144 | padding: 0; } 145 | @media screen and (max-width: 768px) { 146 | .markdown-section code.lang-pathify { 147 | font-size: 5vw; } } 148 | .markdown-section code.lang-pathify .token { 149 | font-style: normal; 150 | font-weight: 400; } 151 | .markdown-section code.lang-pathify .token.comment { 152 | font-size: 0.8em; 153 | vertical-align: bottom; 154 | font-style: italic; 155 | color: #BBB; } 156 | .markdown-section code.lang-pathify .token.string { 157 | color: #25afaf; } 158 | .markdown-section code.lang-pathify .token.number, .markdown-section code.lang-pathify .token.boolean { 159 | color: #BBB; } 160 | .markdown-section code.lang-pathify .token.punctuation { 161 | color: #203245; 162 | padding: 0 .1em; } 163 | .markdown-section code.lang-pathify .token.pathify, .markdown-section code.lang-pathify .token.function, .markdown-section code.lang-pathify .token.keyword { 164 | color: #EB2972; } 165 | .markdown-section code.lang-pathify .token.method { 166 | color: #203245; } 167 | 168 | .cover-main img { 169 | width: 300px; } 170 | 171 | .cover-main blockquote { 172 | margin: 40px 0; 173 | font-family: Baloo, cursive; 174 | text-transform: lowercase; } 175 | .cover-main blockquote p { 176 | margin: 0.5em 0; } 177 | .cover-main blockquote p.title { 178 | margin: 0.3em 0; 179 | font-size: 2.5em; 180 | line-height: 0.8em; } 181 | .cover-main blockquote p.strapline { 182 | font-size: 0.6em; } 183 | 184 | .sidebar > h1 { 185 | text-align: left; 186 | margin-left: 15px; 187 | font-family: Merriweather, serif; 188 | font-weight: 400; } 189 | 190 | .sidebar p.active a { 191 | color: #07a6a6; } 192 | 193 | .sidebar .sidebar-nav > ul > li { 194 | margin-bottom: 30px; 195 | font-size: 17px; } 196 | .sidebar .sidebar-nav > ul > li > p > a { 197 | font-size: 17px; 198 | font-weight: 700; } 199 | 200 | .sidebar > li > p { 201 | font-family: Merriweather, serif; 202 | font-weight: 400; 203 | color: #203245; } 204 | 205 | .sidebar a:hover { 206 | color: #07a6a6; 207 | text-decoration: none; } 208 | 209 | .guide-links { 210 | margin-top: 50px; 211 | border-top: 1px solid #EEE; 212 | padding-top: 20px; } 213 | .guide-links .guide-link-next { 214 | float: right; } 215 | .guide-links .guide-link-prev:before { 216 | padding-right: 0.4em; 217 | content: '←'; } 218 | .guide-links .guide-link-next:after { 219 | padding-left: 0.4em; 220 | content: '→'; } 221 | .guide-links a { 222 | text-decoration: none; } 223 | .guide-links a:hover { 224 | text-decoration: underline; } 225 | -------------------------------------------------------------------------------- /docs/assets/img/demo/demo-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/demo/demo-code.png -------------------------------------------------------------------------------- /docs/assets/img/demo/demo-feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/demo/demo-feature.png -------------------------------------------------------------------------------- /docs/assets/img/demo/demo-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/demo/demo-help.png -------------------------------------------------------------------------------- /docs/assets/img/demo/demo-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/demo/demo-simple.png -------------------------------------------------------------------------------- /docs/assets/img/logos/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/logos/logo.png -------------------------------------------------------------------------------- /docs/assets/img/logos/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logo 5 | Created with Sketch. 6 | 7 | 8 | 85 | 86 | -------------------------------------------------------------------------------- /docs/assets/img/logos/splash-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/logos/splash-landscape.png -------------------------------------------------------------------------------- /docs/assets/img/logos/splash-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/logos/splash-square.png -------------------------------------------------------------------------------- /docs/assets/img/readme/pathify-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/readme/pathify-diagram.png -------------------------------------------------------------------------------- /docs/assets/img/readme/pathify-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/readme/pathify-path.png -------------------------------------------------------------------------------- /docs/assets/img/readme/splash-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/vuex-pathify/4802fd38a4a48d01d576e367789c72e824e323ca/docs/assets/img/readme/splash-github.png -------------------------------------------------------------------------------- /docs/assets/js/plugins.js: -------------------------------------------------------------------------------- 1 | function getLink (link) { 2 | if (link instanceof Element) { 3 | link = link.getAttribute('href') 4 | } 5 | const hash = (link.match(/#.+/) || '').toString().replace('#', '').replace(/\?.*/, '') 6 | return hash || '/' 7 | } 8 | 9 | function getLinks () { 10 | 11 | const sidebar = document.querySelector('.sidebar') 12 | const links = [].slice.call(sidebar.querySelectorAll('a')) 13 | const current = links.find(link => getLink(link) === getLink(location.href)) 14 | const index = links.indexOf(current) 15 | 16 | return { 17 | length: links.length, 18 | current: current, 19 | index: index, 20 | prev: links[index - 1], 21 | next: links[index + 1], 22 | } 23 | } 24 | 25 | function pageNav (hook) { 26 | 27 | function navigate (name) { 28 | const links = getLinks() 29 | const link = links[name] 30 | if (link) { 31 | window.location.hash = getLink(link) 32 | } 33 | } 34 | 35 | hook.init (function () { 36 | window.addEventListener('keydown', function (event) { 37 | if (!event.ctrlKey && !event.metaKey) { 38 | if (event.keyCode === 37) { 39 | navigate('prev') 40 | } 41 | if (event.keyCode === 39) { 42 | navigate('next') 43 | } 44 | } 45 | }) 46 | }) 47 | 48 | } 49 | 50 | function pageLinks (hook) { 51 | 52 | function makeLinks () { 53 | const links = getLinks() 54 | if (!links.current) { 55 | return '' 56 | } 57 | 58 | let html = '' 59 | if (links.prev) { 60 | html += '' +links.prev.outerHTML+ '' 61 | } 62 | if (links.next) { 63 | html += '' +links.next.outerHTML+ '' 64 | } 65 | 66 | return html 67 | } 68 | 69 | hook.afterEach(function (html, next) { 70 | next(html + '') 71 | }) 72 | 73 | hook.ready(function () { 74 | document.querySelector('.guide-links').innerHTML = makeLinks() 75 | }) 76 | 77 | } 78 | 79 | /** 80 | * Fix anchors for all headings with code / methods 81 | * 82 | * @example: 83 | * 84 | * - from: sync(path: string) 85 | * - to: sync 86 | * 87 | * @param hook 88 | */ 89 | function fixAnchors (hook) { 90 | 91 | hook.afterEach(function (html, next) { 92 | 93 | // find all headings and replace them 94 | html = html.replace(/<(h\d).+?<\/\1>/g, function (html) { 95 | 96 | // create temp node 97 | const div = document.createElement('div') 98 | div.innerHTML = html 99 | 100 | // get anchor 101 | const link = div.querySelector('a[href*="?id"]') 102 | if (!link) { 103 | return html 104 | } 105 | 106 | // get id 107 | const matches = link.getAttribute('href').match(/id=(.+)/) 108 | let id = matches[1] 109 | 110 | // clean up ids unless element has `anchor` class (meaning it was manually-entered HTML) 111 | if (!link.classList.contains('anchor')) { 112 | id = link.innerText 113 | .split('(') 114 | .shift() 115 | .toLowerCase() 116 | .replace(/\W+/g, '-') 117 | .replace(/^-+|-+$/g, '') 118 | const href = link.getAttribute('href').replace(/\?id=.+/, '?id=' + id) 119 | link.setAttribute('href', href) 120 | } 121 | 122 | // update dom 123 | link.setAttribute('data-id', id) 124 | link.parentElement.setAttribute('id', id) 125 | 126 | // return html 127 | return div.innerHTML 128 | }) 129 | 130 | // continue 131 | next(html) 132 | }) 133 | } 134 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_code.scss: -------------------------------------------------------------------------------- 1 | pre[data-lang="pathify"], 2 | code.lang-pathify { 3 | margin: 0; 4 | overflow: visible; 5 | background: none; 6 | } 7 | 8 | pre[data-lang="pathify"] { 9 | padding: 2em 2em 4em; 10 | border-radius: 0; 11 | 12 | @media screen and (max-width: $tablet) { 13 | padding: 1em 1em 1.5em; 14 | } 15 | 16 | &:after { 17 | content: none; 18 | } 19 | 20 | &:nth-of-type(1) { 21 | text-align: center !important; 22 | code { 23 | letter-spacing: 0.01em; 24 | font-size: 3.3em; 25 | 26 | @media screen and (max-width: $tablet) { 27 | font-size: 8vw; 28 | } 29 | 30 | } 31 | } 32 | 33 | & + pre[data-lang="pathify"] { 34 | padding-top: 0; 35 | margin-top: -3em; 36 | 37 | @media screen and (max-width: $tablet) { 38 | margin-top: -1em; 39 | } 40 | 41 | } 42 | } 43 | 44 | .token.keyword { 45 | color: $key; 46 | } 47 | 48 | code.lang-pathify, 49 | code.lang-pathify * { 50 | -webkit-font-smoothing: antialiased; 51 | } 52 | 53 | code.lang-pathify { 54 | 55 | line-height: 1.1; 56 | letter-spacing: -0.03em; 57 | color: $dark; 58 | 59 | text-align: left; 60 | display: inline-block; 61 | 62 | font-family: Baloo, Merriweather, Serif; 63 | font-size: 2.2rem; 64 | font-weight: 300; 65 | 66 | @media screen and (max-width: $tablet) { 67 | font-size: 5vw; 68 | } 69 | 70 | padding: 0; 71 | 72 | .token { 73 | 74 | // for ligatures @see https://github.com/TryGhost/Casper/issues/235 75 | font-style: normal; 76 | font-weight: 400; 77 | 78 | &.construct { 79 | } 80 | 81 | &.comment { 82 | font-size: 0.8em; 83 | vertical-align: bottom; 84 | font-style: italic; 85 | color: $light; 86 | } 87 | 88 | &.string { 89 | color: #25afaf; 90 | } 91 | 92 | &.number, 93 | &.boolean { 94 | color: $light; 95 | } 96 | 97 | &.punctuation { 98 | color: $dark; 99 | padding: 0 .1em; 100 | } 101 | 102 | &.pathify, 103 | &.function, 104 | &.keyword { 105 | color: $key2; 106 | // color: $dark; 107 | } 108 | 109 | &.method { 110 | color: $dark; 111 | } 112 | 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_cover.scss: -------------------------------------------------------------------------------- 1 | .cover-main { 2 | 3 | img { 4 | width: 300px; 5 | } 6 | 7 | blockquote { 8 | margin: 40px 0; 9 | font-family: Baloo, cursive; 10 | text-transform: lowercase; 11 | 12 | p { 13 | margin: 0.5em 0; 14 | } 15 | 16 | p.title { 17 | margin: 0.3em 0; 18 | font-size: 2.5em; 19 | line-height: 0.8em; 20 | } 21 | 22 | p.strapline { 23 | font-size: 0.6em; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_formatting.scss: -------------------------------------------------------------------------------- 1 | * { 2 | // -webkit-font-smoothing: antialiased !important; 3 | } 4 | 5 | & { 6 | padding-bottom: 50vh; 7 | } 8 | 9 | // --------------------------------------------------------------------------------------------------------------------- 10 | // HEADINGS 11 | // --------------------------------------------------------------------------------------------------------------------- 12 | 13 | h1, h2, h3, h4, h5 { 14 | font-family: $title-font; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | font-weight: $title-weight; 18 | 19 | a { 20 | font-weight: $title-weight; 21 | transition: 0.2s color; 22 | &.anchor:hover { 23 | text-decoration: none; 24 | span { 25 | color: $link; 26 | } 27 | } 28 | } 29 | } 30 | 31 | h1, h2 { 32 | margin-left: -15px; 33 | padding-left: 15px; 34 | padding-bottom: .2em; 35 | } 36 | 37 | h2 { 38 | border-bottom: 1px solid #EEE; 39 | } 40 | 41 | h1 + h2, 42 | h1 + h3, 43 | h1 + h4, 44 | h1 + blockquote { 45 | margin-top: 0.75em; 46 | } 47 | 48 | h1 { font-size: 3em; } 49 | h2 { font-size: 2.2em; } 50 | h3 { font-size: 1.8em; } 51 | 52 | // --------------------------------------------------------------------------------------------------------------------- 53 | // FORMATTING 54 | // --------------------------------------------------------------------------------------------------------------------- 55 | 56 | table, ul, ol { 57 | margin-left: 25px; 58 | } 59 | 60 | ol li p { 61 | margin-top: 0.5em; 62 | margin-bottom: 0.5em; 63 | 64 | &:last-child { 65 | margin-bottom: 2em; 66 | } 67 | } 68 | 69 | .indent { 70 | margin-left: 20px; 71 | } 72 | 73 | 74 | 75 | @media only screen and (max-width: 600px) { 76 | pre { 77 | margin-left: 0; 78 | } 79 | } 80 | 81 | p.tip + p.tip { 82 | margin-top: -1.5em; 83 | } 84 | 85 | p a:hover { 86 | color: lighten($link, 10%); 87 | } 88 | 89 | em { 90 | color: #34495e; 91 | } 92 | 93 | // --------------------------------------------------------------------------------------------------------------------- 94 | // CODE 95 | // --------------------------------------------------------------------------------------------------------------------- 96 | 97 | pre { 98 | padding: 0; 99 | margin-left: 25px; 100 | 101 | > code { 102 | padding: 10px 15px; 103 | } 104 | 105 | + pre { 106 | margin-top: -.75em; 107 | } 108 | 109 | &:after { 110 | padding: 8px 10px; 111 | } 112 | } 113 | 114 | code, pre { 115 | border-radius: 4px; 116 | color: $key; 117 | } 118 | 119 | h2, h3, h4 { 120 | & code { 121 | font-size: 1em; 122 | font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; 123 | font-weight: 700; 124 | display: inline-block; 125 | background: none; 126 | padding: 0; 127 | margin: 0; 128 | color: inherit; 129 | 130 | &:before { 131 | display: inline-block; 132 | width: 1em; 133 | margin-left: -1em; 134 | content: "#"; 135 | color: #42b983; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_plugins.scss: -------------------------------------------------------------------------------- 1 | .guide-links { 2 | margin-top: 50px; 3 | border-top: 1px solid #EEE; 4 | padding-top: 20px; 5 | 6 | .guide-link-next { 7 | float: right; 8 | } 9 | 10 | .guide-link-prev:before { 11 | padding-right: 0.4em; 12 | content: '←'; 13 | } 14 | 15 | .guide-link-next:after { 16 | padding-left: 0.4em; 17 | content: '→'; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | &:hover { 23 | text-decoration: underline; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | > h1 { 3 | text-align: left; 4 | margin-left: 15px; 5 | font-family: $title-font; 6 | font-weight: $title-weight; 7 | } 8 | 9 | p.active a { 10 | color: $link; 11 | } 12 | 13 | .sidebar-nav > ul > li { 14 | margin-bottom: 30px; 15 | font-size: 17px; 16 | 17 | > p > a { 18 | font-size: 17px; 19 | font-weight: 700; 20 | } 21 | } 22 | 23 | > li > p { 24 | font-family: $title-font; 25 | font-weight: 400; 26 | color: $dark; 27 | } 28 | 29 | a:hover { 30 | color: $link; 31 | text-decoration: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/assets/scss/partials/_variables.scss: -------------------------------------------------------------------------------- 1 | $lightest: #DDD; 2 | $light: #BBB; 3 | $mid: #666; 4 | $key: #ff7337; 5 | $key2: #EB2972; 6 | $link: #07a6a6; 7 | $dark: #203245; 8 | 9 | $title-font: Merriweather, serif; 10 | $title-weight: 400; 11 | 12 | $tablet: 768px; 13 | -------------------------------------------------------------------------------- /docs/assets/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | 3 | .content { 4 | padding-top: 30px; 5 | } 6 | 7 | .markdown-section { 8 | @import "partials/formatting"; 9 | @import "partials/code"; 10 | } 11 | 12 | @import "partials/cover"; 13 | @import "partials/sidebar"; 14 | @import "partials/plugins"; 15 | -------------------------------------------------------------------------------- /docs/assets/vendor/docsify/merriweather.css: -------------------------------------------------------------------------------- 1 | /* cyrillic-ext */ 2 | @font-face { 3 | font-family: 'Merriweather'; 4 | font-style: italic; 5 | font-weight: 300; 6 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR7lXff1jvzDP3WGO5g.woff2) format('woff2'); 7 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 8 | } 9 | /* cyrillic */ 10 | @font-face { 11 | font-family: 'Merriweather'; 12 | font-style: italic; 13 | font-weight: 300; 14 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR7lXff8jvzDP3WGO5g.woff2) format('woff2'); 15 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 16 | } 17 | /* vietnamese */ 18 | @font-face { 19 | font-family: 'Merriweather'; 20 | font-style: italic; 21 | font-weight: 300; 22 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR7lXff3jvzDP3WGO5g.woff2) format('woff2'); 23 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 24 | } 25 | /* latin-ext */ 26 | @font-face { 27 | font-family: 'Merriweather'; 28 | font-style: italic; 29 | font-weight: 300; 30 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR7lXff2jvzDP3WGO5g.woff2) format('woff2'); 31 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 32 | } 33 | /* latin */ 34 | @font-face { 35 | font-family: 'Merriweather'; 36 | font-style: italic; 37 | font-weight: 300; 38 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR7lXff4jvzDP3WG.woff2) format('woff2'); 39 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 40 | } 41 | /* cyrillic-ext */ 42 | @font-face { 43 | font-family: 'Merriweather'; 44 | font-style: italic; 45 | font-weight: 400; 46 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4m0qyriQwlOrhSvowK_l5-eRZDf-LVrPHpBXw.woff2) format('woff2'); 47 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 48 | } 49 | /* cyrillic */ 50 | @font-face { 51 | font-family: 'Merriweather'; 52 | font-style: italic; 53 | font-weight: 400; 54 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4m0qyriQwlOrhSvowK_l5-eRZKf-LVrPHpBXw.woff2) format('woff2'); 55 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 56 | } 57 | /* vietnamese */ 58 | @font-face { 59 | font-family: 'Merriweather'; 60 | font-style: italic; 61 | font-weight: 400; 62 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4m0qyriQwlOrhSvowK_l5-eRZBf-LVrPHpBXw.woff2) format('woff2'); 63 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 64 | } 65 | /* latin-ext */ 66 | @font-face { 67 | font-family: 'Merriweather'; 68 | font-style: italic; 69 | font-weight: 400; 70 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4m0qyriQwlOrhSvowK_l5-eRZAf-LVrPHpBXw.woff2) format('woff2'); 71 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 72 | } 73 | /* latin */ 74 | @font-face { 75 | font-family: 'Merriweather'; 76 | font-style: italic; 77 | font-weight: 400; 78 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4m0qyriQwlOrhSvowK_l5-eRZOf-LVrPHp.woff2) format('woff2'); 79 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 80 | } 81 | /* cyrillic-ext */ 82 | @font-face { 83 | font-family: 'Merriweather'; 84 | font-style: italic; 85 | font-weight: 700; 86 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR71Wvf1jvzDP3WGO5g.woff2) format('woff2'); 87 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 88 | } 89 | /* cyrillic */ 90 | @font-face { 91 | font-family: 'Merriweather'; 92 | font-style: italic; 93 | font-weight: 700; 94 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR71Wvf8jvzDP3WGO5g.woff2) format('woff2'); 95 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 96 | } 97 | /* vietnamese */ 98 | @font-face { 99 | font-family: 'Merriweather'; 100 | font-style: italic; 101 | font-weight: 700; 102 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR71Wvf3jvzDP3WGO5g.woff2) format('woff2'); 103 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 104 | } 105 | /* latin-ext */ 106 | @font-face { 107 | font-family: 'Merriweather'; 108 | font-style: italic; 109 | font-weight: 700; 110 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR71Wvf2jvzDP3WGO5g.woff2) format('woff2'); 111 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 112 | } 113 | /* latin */ 114 | @font-face { 115 | font-family: 'Merriweather'; 116 | font-style: italic; 117 | font-weight: 700; 118 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4l0qyriQwlOrhSvowK_l5-eR71Wvf4jvzDP3WG.woff2) format('woff2'); 119 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 120 | } 121 | /* cyrillic-ext */ 122 | @font-face { 123 | font-family: 'Merriweather'; 124 | font-style: normal; 125 | font-weight: 300; 126 | src: local('Merriweather Light'), local('Merriweather-Light'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZVcf6hPvhPUWH.woff2) format('woff2'); 127 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 128 | } 129 | /* cyrillic */ 130 | @font-face { 131 | font-family: 'Merriweather'; 132 | font-style: normal; 133 | font-weight: 300; 134 | src: local('Merriweather Light'), local('Merriweather-Light'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZXMf6hPvhPUWH.woff2) format('woff2'); 135 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 136 | } 137 | /* vietnamese */ 138 | @font-face { 139 | font-family: 'Merriweather'; 140 | font-style: normal; 141 | font-weight: 300; 142 | src: local('Merriweather Light'), local('Merriweather-Light'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZV8f6hPvhPUWH.woff2) format('woff2'); 143 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 144 | } 145 | /* latin-ext */ 146 | @font-face { 147 | font-family: 'Merriweather'; 148 | font-style: normal; 149 | font-weight: 300; 150 | src: local('Merriweather Light'), local('Merriweather-Light'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZVsf6hPvhPUWH.woff2) format('woff2'); 151 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 152 | } 153 | /* latin */ 154 | @font-face { 155 | font-family: 'Merriweather'; 156 | font-style: normal; 157 | font-weight: 300; 158 | src: local('Merriweather Light'), local('Merriweather-Light'), url(https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZWMf6hPvhPQ.woff2) format('woff2'); 159 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 160 | } 161 | /* cyrillic-ext */ 162 | @font-face { 163 | font-family: 'Merriweather'; 164 | font-style: normal; 165 | font-weight: 400; 166 | src: local('Merriweather Regular'), local('Merriweather-Regular'), url(https://fonts.gstatic.com/s/merriweather/v19/u-440qyriQwlOrhSvowK_l5-cSZMdeX3rsHo.woff2) format('woff2'); 167 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 168 | } 169 | /* cyrillic */ 170 | @font-face { 171 | font-family: 'Merriweather'; 172 | font-style: normal; 173 | font-weight: 400; 174 | src: local('Merriweather Regular'), local('Merriweather-Regular'), url(https://fonts.gstatic.com/s/merriweather/v19/u-440qyriQwlOrhSvowK_l5-eCZMdeX3rsHo.woff2) format('woff2'); 175 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 176 | } 177 | /* vietnamese */ 178 | @font-face { 179 | font-family: 'Merriweather'; 180 | font-style: normal; 181 | font-weight: 400; 182 | src: local('Merriweather Regular'), local('Merriweather-Regular'), url(https://fonts.gstatic.com/s/merriweather/v19/u-440qyriQwlOrhSvowK_l5-cyZMdeX3rsHo.woff2) format('woff2'); 183 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 184 | } 185 | /* latin-ext */ 186 | @font-face { 187 | font-family: 'Merriweather'; 188 | font-style: normal; 189 | font-weight: 400; 190 | src: local('Merriweather Regular'), local('Merriweather-Regular'), url(https://fonts.gstatic.com/s/merriweather/v19/u-440qyriQwlOrhSvowK_l5-ciZMdeX3rsHo.woff2) format('woff2'); 191 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 192 | } 193 | /* latin */ 194 | @font-face { 195 | font-family: 'Merriweather'; 196 | font-style: normal; 197 | font-weight: 400; 198 | src: local('Merriweather Regular'), local('Merriweather-Regular'), url(https://fonts.gstatic.com/s/merriweather/v19/u-440qyriQwlOrhSvowK_l5-fCZMdeX3rg.woff2) format('woff2'); 199 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 200 | } 201 | -------------------------------------------------------------------------------- /docs/assets/vendor/docsify/search.min.js: -------------------------------------------------------------------------------- 1 | !function(){var n,e={};function t(n){var e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return String(n).replace(/[&<>"'/]/g,function(n){return e[n]})}function o(t,o){n=Docsify;var a="auto"===t.paths,i=localStorage.getItem("docsify.search.expires")l.length&&(d=l.length);var h="..."+t(l).substring(i,d).replace(e,''+n+"")+"...";s+=h}}),r)){var h={title:t(c),content:s,url:d};o.push(h)}},s=0;s\n

'+n.title+"

\n

"+n.content+"

\n\n"}),i.classList.add("show"),r.classList.add("show"),i.innerHTML=s||'

'+a+"

"}function r(n,e){var t=e.router.parse().query.s;Docsify.dom.style("\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0 7px;\n line-height: 36px;\n font-size: 14px;\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.search .clear-button {\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}"),function(n,e){void 0===e&&(e="");var t='
\n \n
\n \n \n \n \n \n
\n
\n
\n ',o=Docsify.dom.create("div",t),a=Docsify.dom.find("aside");Docsify.dom.toggleClass(o,"search"),Docsify.dom.before(a,o)}(0,t),function(){var n,e=Docsify.dom.find("div.search"),t=Docsify.dom.find(e,"input"),o=Docsify.dom.find(e,".input-wrap");Docsify.dom.on(e,"click",function(n){return"A"!==n.target.tagName&&n.stopPropagation()}),Docsify.dom.on(t,"input",function(e){clearTimeout(n),n=setTimeout(function(n){return i(e.target.value.trim())},100)}),Docsify.dom.on(o,"click",function(n){"INPUT"!==n.target.tagName&&(t.value="",i())})}(),t&&setTimeout(function(n){return i(t)},500)}function s(n,e){!function(n,e){var t=Docsify.dom.getNode('.search input[type="search"]');if(t)if("string"==typeof n)t.placeholder=n;else{var o=Object.keys(n).filter(function(n){return e.indexOf(n)>-1})[0];t.placeholder=n[o]}}(n.placeholder,e.route.path),function(n,e){if("string"==typeof n)a=n;else{var t=Object.keys(n).filter(function(n){return e.indexOf(n)>-1})[0];a=n[t]}}(n.noData,e.route.path)}var c={placeholder:"Type to search",noData:"No Results!",paths:"auto",depth:2,maxAge:864e5};$docsify.plugins=[].concat(function(n,e){var t=Docsify.util,a=e.config.search||c;Array.isArray(a)?c.paths=a:"object"==typeof a&&(c.paths=Array.isArray(a.paths)?a.paths:"auto",c.maxAge=t.isPrimitive(a.maxAge)?a.maxAge:c.maxAge,c.placeholder=a.placeholder||c.placeholder,c.noData=a.noData||c.noData,c.depth=a.depth||c.depth);var i="auto"===c.paths;n.mounted(function(n){r(0,e),!i&&o(c,e)}),n.doneEach(function(n){s(c,e),i&&o(c,e)})},$docsify.plugins)}(); 2 | -------------------------------------------------------------------------------- /docs/assets/vendor/docsify/vue.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:5}.search .search-keyword,.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}.task-list-item{list-style-type:none}li input[type=checkbox]{margin:0 .2em .25em -1.6em;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:2}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:0;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:a .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:3}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:4}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:a .56s ease-in-out}}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{-ms-flex-align:center;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover.show{display:-ms-flexbox;display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{-ms-flex:1;flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border:1px solid var(--theme-color,#42b983);border-radius:2rem;box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0 6px 15px}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0} -------------------------------------------------------------------------------- /docs/assets/vendor/ga.min.js: -------------------------------------------------------------------------------- 1 | !function(){function n(n){!function(){var n=document.createElement("script");n.async=!0,n.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(n)}(),window.ga=window.ga||function(){(window.ga.q=window.ga.q||[]).push(arguments)},window.ga.l=Number(new Date),window.ga("create",n,"auto")}function o(){window.ga||n($docsify.ga),window.ga("set","page",location.hash),window.ga("send","pageview")}$docsify.plugins=[].concat(function(n){$docsify.ga?n.beforeEach(o):console.error("[Docsify] ga is required.")},$docsify.plugins)}(); 2 | -------------------------------------------------------------------------------- /docs/assets/vendor/prism/prism-pathify.js: -------------------------------------------------------------------------------- 1 | function only (words) { 2 | return new RegExp('\\b(?:' + String(words).match(/\w+/g).join('|') + ')\\b') 3 | } 4 | 5 | Prism.languages.pathify = Prism.languages.extend('javascript', { 6 | 'construct' : only('var|const|function|default|extends|implements|import|interface|internal|package|private|protected|public|use|dynamic|final|include|namespace|override|static'), 7 | 'pathify' : only('get|set|sync'), 8 | 'method' : only('mutations|actions|getters'), 9 | 'keyword' : only('as|break|case|catch|class|delete|do|else|finally|for|if|in|instanceof|is|native|new|null|return|super|switch|this|throw|try|typeof|void|while|with|each'), 10 | 'operator' : /\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<>?>?|[!=]=?)=?|[~?@]/ 11 | }); 12 | Prism.languages.pathify['class-name'].alias = 'function'; 13 | 14 | if (Prism.languages.markup) { 15 | Prism.languages.insertBefore('pathify', 'string', { 16 | 'xml': { 17 | pattern: /(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/, 18 | lookbehind: true, 19 | inside: { 20 | rest: Prism.languages.markup 21 | } 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vuex Pathify - ridiculously simple Vuex setup + wiring 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
 
34 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/pages/cover.md: -------------------------------------------------------------------------------- 1 | ![logo](../assets/img/logos/logo.svg) 2 | 3 |
4 |

Vuex Pathify

5 |

Ridiculously simple Vuex setup + wiring

6 |
7 | 8 | [GitHub](https://github.com/davestewart/vuex-pathify) 9 | [Get Started](#home) 10 | 11 | 12 | ![color](#FFFFFF) -------------------------------------------------------------------------------- /docs/pages/discussion/faq.md: -------------------------------------------------------------------------------- 1 | # FAQs 2 | 3 | > Pathify and Vuex tips and tricks to ramp up your productivity 4 | 5 | 6 | 7 | ## When to use Pathify 8 | 9 | #### What is Pathify best-suited for? 10 | 11 | Getting, setting and syncing properties 1:1 with the store. 12 | 13 | In the vast majority of cases, you'll just need to set and get data, or sync components, and this is where Pathify shines. 14 | 15 | #### What is Pathify less-suited for? 16 | 17 | Because Pathify's configuration is tuned to map state names to Vuex members in a `get/set` manner, calling non `get/set` named `actions` or `mutations` such as `updateItems` or `loadItems` via Pathify may not feel intuitive. 18 | 19 | You can use [direct syntax](/guide/paths#direct-syntax) to target non-`get/set` members, for example `set('products/updateItem!', value)` or, if you feel more comfortable in these situations commit or dispatch [directly](#do-i-still-need-commit-and-dispatch). 20 | 21 | #### Make sure you understand Vuex properly before using Pathify 22 | 23 | Pathify wires up Vuex "behind the scenes", so if you don't properly understand Vuex, you'll doubtless find it confusing if something doesn't work as expected. 24 | 25 | Pathify isn't designed to replace Vuex, or designed "let you off" understanding Vuex, it's designed to take the drudgery out of wiring up Vuex stores once you understand them. 26 | 27 | #### Don't use Pathify sub-property access as a crutch 28 | 29 | Pathify's sub-property access is extremely useful, but don't be tempted to put everything in one property, then use pathify to access sub properties, as your application logic will become unclear. 30 | 31 | 32 | 33 | ## Store setup 34 | 35 | #### Should I always go through getters and actions? 36 | 37 | There's really no need! 38 | 39 | The consistency that **in theory** was provided by always going through actions or always going through getters, is mitigated by **only going through a path**. 40 | 41 | Creating additional getters and actions will only clog up your store and provide more code for Vue to run when getting or setting values. Creating ONLY the getters and actions you need keeps your store lean and codebase tight! 42 | 43 | The [store helpers](/guide/store) include `make.getters()` and `make.actions()` mainly for developers who [prefer this approach](https://forum.vuejs.org/t/actions-for-actions-sake/16413). 44 | 45 | 46 | #### Which naming scheme should I use; default or simple? 47 | 48 | If you can, name your store members using the [simple](/setup/mapping) scheme. 49 | 50 | It makes no difference to Pathify when correctly configured, but in the author's opinion, it's much easier to scan a file and see relate a single `foo` in state, getters and mutations, than it is to see 2 or even 3 variations on prefixing, naming and casing. 51 | 52 | Additionally, if you do need to use `commit` or `action` syntax either directly or in Pathify, it feels more consistent to use and type `lowercase` or `camelCase` in components, rather than have to keep swapping between `foo` and `SET_FOO`. 53 | 54 | 55 | Finally, in the Vuex Devtools mutations panel it's arguably easier on the eyes to scan a list of consistent `foo/bar` type mutations than it is to read a mixed list of `foo/SET_BAR` type mutations! 56 | 57 | 58 | ## Component wiring 59 | 60 | #### Do I still need commit and dispatch? 61 | 62 | With `sync()`, `set()` and [direct syntax](/guide/paths#direct-syntax) being the preferred way to get and set values on the store, you won't need `commit()` as much, but `dispatch()` is still useful in the fact it's explicit. 63 | 64 | As such, Pathify includes aliases to `commit()` and `dispatch()` within its helpers; it's up to you which method you feel most comfortable with. 65 | 66 | See the [Vuex aliases](api/properties#vuex-aliases) section for more info. 67 | 68 | 69 | #### Should I use Vuex Helpers? 70 | 71 | No. 72 | 73 | Vuex helpers, though saving over manual coding, add a lot of structural and syntactic cruft; up to 16 different entities to think about vs only 7 for Pathify. See the [code comparison](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=code/large) section of the demo to view this for yourself. 74 | 75 | In Pathify 1.1 the addition of the `call()` helper allows you to [map actions](api/component?id=call) without needing Vuex Helpers, with the additional flexibility of using wildcards. 76 | 77 | 78 | #### Do I ever need to access the store directly? 79 | 80 | Note that although Pathify removes the burden of setup and wiring, there will still be times when you want to access the store explicitly. 81 | 82 | Some rules of thumb:: 83 | 84 | - use Pathify `get()`, `set()` and `sync()` for global access and component/store sync 85 | - use Pathify [direct syntax](/guide/paths#direct-syntax) for non `set*`/`SET_*` mutations or actions 86 | - use Vuex `dispatch()` or `commit()` if you really want to be explicit, or **within** the store 87 | - call `state` directly if a same-named getter is taking priority 88 | 89 | 90 | 91 | 92 | 93 | ## Debugging 94 | 95 | #### How much complication is generated by, or in, helper functions? 96 | 97 | None, really. 98 | 99 | After the members have been found, returned functions are lightweight, and references are concrete. 100 | 101 | #### Does Pathify make it harder to debug? 102 | 103 | Pathify actually has a bunch of built-in warnings for when you don't wire things correctly! If you make a mistake, Pathify will attempt to help you out. 104 | 105 | Once set-up in the store, or wired in components, the functions are literally the same functions as you would have written yourself. 106 | 107 | If you come across a situation where you think Pathify has made something harder, [raise an issue](https://github.com/davestewart/vuex-pathify/issues) on Github. 108 | -------------------------------------------------------------------------------- /docs/pages/discussion/rationale.md: -------------------------------------------------------------------------------- 1 | # Rationale 2 | 3 | > “If something can be easy, why make it hard?” - Evan You 4 | 5 | ## Overview 6 | 7 | Why was Pathify created? 8 | 9 | The rationale behind creating Pathify is that **Vuex is great** but the coding experience is **way too complex** for something as simple as **setting and getting values** on what is essentially a global object. 10 | 11 | Managing state should not feel like an episode of [American Ninja Warrior](https://www.youtube.com/watch?v=gZkObiYVdN4). We should not need to be constantly checking the manual for syntax or caveats, writing reams of redundant code, or fretting over which "best practice" will protect us from ourselves long enough to make an app that functions in the real world. 12 | 13 | With that being said, Vuex is the Vue **state management standard**, has great tooling and online support, so we want to work with it rather than coming up with a new solution that no-one knows or cares about. 14 | 15 | ## The brief 16 | 17 | ### Pain points 18 | 19 | The first thing to look at was, where are the pain points with setting and getting data in Vuex? 20 | 21 | Which ones can be solved, which ones seem to be mantra over practicality, and which ones do we just have to live with? 22 | 23 | - Lots of store boilerplate 24 | 25 | - every property needing to be explicitly created 26 | - every property needs at least a state and mutation, and potentially a getter and action 27 | - unsure whether to create getters for all states, or not 28 | - when should actions be created and when to use only mutations ? 29 | 30 | - Component wiring 31 | 32 | - many competing and cumbersome component wiring choices 33 | - 4 Vuex helpers to choose from 34 | - why do we have to map state AND getters ? 35 | - when and where to use "mapMutations" vs "mapActions" ? 36 | - helpers needed in computed and methods 37 | 38 | - Things that are hard but feel like they shouldn't be 39 | 40 | - overall setup 41 | - working with object sub-properties 42 | - choosing between similar store members (i.e. state & getters) 43 | 44 | - Inconsistency 45 | 46 | - In stores, why `items`, `setItems` and `SET_ITEMS` ? 47 | - In components, why: 48 | - `store.state.module.value` and `store.getters['module/value']` 49 | - `commit('module/SET_VALUE')` and `dispatch('module/setValue')` 50 | 51 | - Varying advice on "best" practices 52 | 53 | - it's "bad practice" to use mutations in components 54 | - you should use "getters and actions" not "state and mutations" 55 | - should you write commits as `CONST_CASE` or `camelCase` ? 56 | 57 | - Verbose setup 58 | - storing different accessor types in separate files 59 | - creating static consts for all store member references 60 | - using dynamic object keys e.g. `[mutations.SET_ITEMS]: function () { ... }` to create properties 61 | 62 | - Esoteric terms and concepts 63 | 64 | - why "mutation", "commit", "dispatch" ? 65 | - why "getters" but then "mutations" 66 | - when to use actions and when mutations ? 67 | - when to use getters and when state ? 68 | 69 | The above points seem to fall into the following categories: 70 | 71 | - **experience** - some of these points become clearer as you get to know Vuex 72 | - **architecture** - some points are concerned simply with Vuex's architectural choices (good or bad) 73 | - **best practice** - seemingly "best" practices seem to exist to protect the developer against the code itself 74 | - **personal preference** - some of these points will depend on yours or your team's developmental preferences 75 | 76 | In the next section we'll discuss which of these can be mitigated against, but for now, let's see what we both need and want to do with Vuex... 77 | 78 | ### Everyday Vuex 79 | 80 | The second thing to look at is, what are the general tasks we do with Vuex on a day to day basis? 81 | 82 | What do we need a proposed solution to actively support, or at least not get in the way of? 83 | 84 | - Writing boilerplate: 85 | 86 | - setting up mutations for all states 87 | - potentially, setting up getters and actions 88 | - wiring one-way getters for component properties, either: 89 | - manually, by writing functions and accessing state and/or getters 90 | - automatically, using mapState and mapGetters 91 | - setting up two way wiring for component controls, either: 92 | - manually, by writing compound computed properties 93 | - semi-automatically, using 94 | - a combination of @event handlers 95 | - mapState / mapGetters 96 | - mapMutations / mapActions 97 | 98 | - General access 99 | 100 | - getting values from state or getters 101 | - setting values using commits or dispatches 102 | - calling actions using dispatches 103 | 104 | - State management 105 | 106 | - managing copies and part copies, for forms, or other compound components 107 | - managing grouped options, such as sorting, search, etc 108 | 109 | 110 | ### Ideal solution 111 | 112 | What would the ideal Vuex experience look like ? 113 | 114 | - Baseline happy: 115 | 116 | - not have to juggle JavaScript syntax or Vuex naming formats 117 | - not have to think about which store member data comes from or has to go to 118 | - set and get data without constant hand-holding from the documentation or forums 119 | - set up wiring quickly, easily and consistently 120 | - reduce or eliminate store boilerplate 121 | 122 | - Ideal scenario: 123 | 124 | - works in any project 125 | - works with, not against, Vuex 126 | - doesn't require hacks to get working 127 | - doesn't require compromises in Vuex or JavaScript 128 | - can be integrated a little or a lot 129 | - significant reduction in lines of code 130 | - removes need for defensive programming 131 | - adds additional useful functionality 132 | - approved by advanced users 133 | - easy for beginners 134 | 135 | 136 | ## Result 137 | 138 | Pathify is the result of around 6 months of incremental development, on one large project and a couple of smaller ones. It morphed from being a kitchen-sink of tools and plugins (including state persistence, store lifecycle, and transmitting state by the URL) to being only concerned with getting and setting state via paths. 139 | 140 | Looking at the list above, we can see that Pathify solves the following: 141 | 142 | 1. **Syntax juggling** 143 | 144 | [Path syntax](/guide/paths.md) unifies differing varied state, getters, commit and dispatch syntax. 145 | 146 | [Accessor priority](/guide/properties.md#accessor-priority) decouples store access from store architecture and makes it simple to get or set. 147 | 148 | 2. **Boilerplate** 149 | 150 | [Store helpers](/guide/store.md) eliminate store boilerplate. 151 | 152 | 3. **Wiring** 153 | 154 | [Component helpers](/guide/component.md) eliminate wiring boilerplate. 155 | 156 | [Sub-property access](/guide/properties.md#sub-property-access) enables transparent read and write for complex properties. 157 | 158 | 4. **Works in any project** 159 | 160 | [Mapping configuration](/setup/mapping.md) enables property mapping in any project. 161 | 162 | [Direct-property access](/guide/properties.md#direct-property-access) handles unmappable properties. 163 | 164 | 5. **Simplicity** 165 | 166 | 4 Vuex operations simplified to 2 Pathify operations. 167 | 168 | Path format does away with 3 different syntax types. 169 | 170 | Setting up and wiring properties takes 1 line per-file for multiple properties, not 3 - 8 lines per-property, per-file. 171 | 172 | Depending on your developmental / team requirements, no need for redundant or complex "best practices". 173 | 174 | Easy for beginners. 175 | -------------------------------------------------------------------------------- /docs/pages/guide/accessors.md: -------------------------------------------------------------------------------- 1 | # Store accessors 2 | 3 | > Store accessors provide simple read / write access directly on the store 4 | 5 | ## Overview 6 | 7 | The plugin adds accessor methods directly to the Vuex store instance. 8 | 9 | The methods: 10 | 11 | - use Pathify's [path syntax](/guide/paths.md) to reference modules, properties and sub-properties 12 | - implement [accessor priority](/guide/properties.md#accessor-priority), simplifying the overall set/get interface 13 | - couple with [store helpers](/guide/store.md) to provide full sub-property read/write 14 | 15 | 16 | ## Usage 17 | 18 | Once [configured](/setup/config.md), getting and setting values on the store is simple: 19 | 20 | ```js 21 | store.set('filters@sort.order', 'asc') 22 | ``` 23 | 24 | You can get and set values from anywhere, even the console (which is great for debugging!): 25 | 26 | ```js 27 | import store from 'store' 28 | window.store = store 29 | ``` 30 | ```console 31 | store.set('filters@search', 'widgets') 32 | ``` 33 | 34 | See the store accessors [demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=api/accessors) for an editable, live example. 35 | 36 | ## API 37 | 38 | ### Methods 39 | 40 | Remember that store accessors use Pathify's core [property access](/guide/properties.md) so have exactly the same functionality. 41 | 42 | #### `get(path: string): *` 43 | 44 | The `get()` method reads values from the getters or state: 45 | 46 | ```js 47 | // 'loading' 48 | store.get('status') 49 | ``` 50 | ```js 51 | // 'asc' 52 | store.get('filters@sort.order') 53 | ``` 54 | 55 | If the path references a store [getter function](https://vuex.vuejs.org/en/getters.html#method-style-access), pass additional arguments as required: 56 | 57 | ```js 58 | const bags = store.get('filterBy', 'category', 'bag') 59 | ``` 60 | 61 | #### `set(path: string, value: *): *` 62 | 63 | The `set()` method writes values via actions or mutations: 64 | 65 | ```js 66 | store.set('status', 'error') 67 | ``` 68 | ```js 69 | store.set('filters@sort.order', 'desc') 70 | ``` 71 | 72 | Note that `set()` returns the result of the operation so if you're expecting a Promise, you can continue when it resolves: 73 | 74 | ```js 75 | store 76 | .set('items', data) 77 | .then(console.log) 78 | ``` 79 | 80 | 81 | 82 | #### `copy(path: string): *` 83 | 84 | The `copy()` method clones and returns a non-reactive copy of the values in the store. 85 | 86 | ```js 87 | // { key: "id", order: "asc" } 88 | copy('sort') 89 | ``` 90 | ```js 91 | // rather than {__ob__: Observer} 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /docs/pages/guide/component.md: -------------------------------------------------------------------------------- 1 | # Component helpers 2 | 3 | > Component helpers take the pain out of wiring 4 | 5 | ## Overview 6 | 7 | Pathify component helpers are designed to **easily wire components** to the store. 8 | 9 | They are **direct replacements** for Vuex `map*()` helpers, which wire: 10 | 11 | - computed properties to state / getters 12 | - methods to actions 13 | - single or multiple members 14 | - 1-way and 2-way data-bindings 15 | 16 | Additionally, they support Pathify's rich [path syntax](api/paths) including: 17 | 18 | - sub-property access 19 | - wildcards 20 | 21 | Each helper generates and returns the appropriate `Function` handler or `Object` hash of handlers, which can be directly assigned or spread in as needed. 22 | 23 | Note that although the **generation** is more expensive than writing a manual function, once helper has finished, only lightweight functions are returned and run. 24 | 25 | 26 | ## Usage 27 | 28 | The following gives an example of some of the main features: 29 | 30 | ```js 31 | import { get, sync, call } from 'vuex-pathify' 32 | 33 | // component 34 | export default { 35 | computed: { 36 | // read-only, single property 37 | items: get('products/items'), 38 | 39 | // read/write, single property 40 | search: sync('products/filters@search'), 41 | 42 | // read/write, rename, multiple (sub) properties 43 | ...sync('products/filters@sort', { 44 | sortOrder: 'order', 45 | sortKey: 'key', 46 | }), 47 | 48 | // read/write, multiple automatic properties 49 | ...sync('products/*') 50 | }, 51 | 52 | methods: { 53 | // wire multiple actions 54 | ...call('products/*') 55 | } 56 | } 57 | ``` 58 | 59 | See the component helpers [demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=api/component) for an editable, live example. 60 | 61 | 62 | ## API 63 | 64 | !> Remember that component helpers use Pathify's core [property access](/guide/properties.md) thus have exactly the same functionality. 65 | 66 | ### Single property access 67 | 68 | #### `get(path: string): *` 69 | 70 | Use `get()` to read properties from the store: 71 | 72 | ```js 73 | computed: { 74 | items: get('products/items') 75 | } 76 | ``` 77 | 78 | The helper generates the following computed property: 79 | 80 | ```js 81 | computed: { 82 | items () { 83 | return this.$store.getters['products/items'] 84 | } 85 | } 86 | ``` 87 | 88 | The function is analogous to a combination of `mapState()` and `mapGetters()`. 89 | 90 | #### `sync(path: string): *` 91 | 92 | Use `sync()` to set up two-way data binding: 93 | 94 | ```js 95 | computed: { 96 | items: sync('products/status') 97 | } 98 | ``` 99 | 100 | The helper generates the following **compound** computed property: 101 | 102 | ```js 103 | computed: { 104 | status: { 105 | get () { 106 | return this.$store.state.products.status 107 | }, 108 | set (value) { 109 | return this.$store.commit('products/SET_STATUS', value) 110 | }, 111 | } 112 | } 113 | ``` 114 | 115 | The function is analogous to a combination of `mapState()` and `mapMutations()`. 116 | 117 | Note that `sync()` reads **only from state** to prevent a situation where a same-named [transformational getter](api/properties?id=accessor-priority) could end up recursively modifying the true value of state. 118 | 119 | If you want to specify an alternate mutation or action, `sync()` takes an additional path syntax `|` with which you can specify direct access: 120 | 121 | ```js 122 | computed: { 123 | // get with `items` accessor but set with `update()` action 124 | items: sync('items|update') 125 | } 126 | ``` 127 | 128 | #### `call(path: string): *` 129 | 130 | Use `call()` to create functions that dispatch actions to the store: 131 | 132 | ```js 133 | methods: { 134 | load: call('products/load') 135 | } 136 | ``` 137 | 138 | The helper generates the following method: 139 | 140 | ```js 141 | methods: { 142 | load (payload) { 143 | return this.$store.dispatch('products/load', payload) 144 | } 145 | } 146 | ``` 147 | 148 | The function is analogous to `mapActions()`. 149 | 150 | 151 | ### Multi-property access 152 | 153 | Each of the component helpers can generate **multiple** members. 154 | 155 | You can use: 156 | 157 | - [array syntax](#array-syntax) - to map properties 1:1 with the store 158 | - [object syntax](#object-syntax) - to map properties with different names on the component 159 | - [wildcard expansion](#wildcard-expansion) - to grab sets of properties automatically 160 | 161 | Each syntax generates an **Object** of **named properties** which must be mixed in to the associated block (`computed` or `methods`) or set as the block itself: 162 | 163 | ```js 164 | computed: { 165 | ...sync(map), 166 | ...sync(path, map) 167 | }, 168 | methods: { 169 | ...call(map), 170 | ...call(path, map) 171 | } 172 | ``` 173 | ```js 174 | computed: sync(path, map), 175 | methods: call(path, map) 176 | ``` 177 | 178 | #### `Array syntax` 179 | 180 | Array syntax maps property names **identically** to the store. 181 | 182 | It takes an **optional** `path` prefix, and an array of store `member` names: 183 | 184 | ```js 185 | computed: { 186 | ...get('products', [ 187 | 'search', 188 | 'items', 189 | ]) 190 | }, 191 | methods: { 192 | ...get('products', [ 193 | 'load', 194 | 'update', 195 | ]) 196 | } 197 | ``` 198 | ```paths 199 | items : products/items 200 | filter : products/filter 201 | load : products/load() 202 | update : products/update() 203 | ``` 204 | 205 | #### `Object syntax` 206 | 207 | Object syntax maps property names **differently** to the store. 208 | 209 | It takes an **optional** `path` prefix, and hash of `key:member` names: 210 | 211 | ```js 212 | computed: { 213 | ...sync('products/filters@sort', { 214 | sortOrder: 'order', 215 | sortKey: 'key', 216 | }) 217 | }, 218 | methods: { 219 | ...call('products', { 220 | loadItems: 'load', 221 | updateItems: 'update', 222 | }) 223 | } 224 | ``` 225 | ```paths 226 | sortOrder : products/filters@sort.order 227 | sortKey : products/filters@sort.key 228 | loadItems : products/load() 229 | updateItems : products/update() 230 | ``` 231 | 232 | 233 | #### `Wildcard syntax` 234 | 235 | Wildcard syntax maps **groups** of property names **identically** to the store. 236 | 237 | ```js 238 | computed: { 239 | ...get('products/*') 240 | }, 241 | methods: { 242 | ...call('products/*') 243 | } 244 | ``` 245 | ```paths 246 | items : products/items 247 | search : products/search 248 | filters : products/filters 249 | load : products/load() 250 | update : products/update() 251 | ``` 252 | 253 | Additionally, computed properties can target **any** set of state properties or sub-properties, so the following are all valid: 254 | 255 | ```wildcards 256 | products/* 257 | products/filters@* 258 | products/filters@sort.* 259 | ``` 260 | 261 | Note that the path engine supports partial matches: 262 | 263 | ```js 264 | methods: { 265 | ...call('products/*Items') 266 | }, 267 | ``` 268 | 269 | However, overuse of partial matching would indicate an over-complex design and the need for refactoring! 270 | 271 | ### Troubleshooting wildcards 272 | 273 | Before you get all trigger-happy with wildcards and use them everywhere, there are a few things you should know. 274 | 275 | The wildcard `*` symbol tells Pathify that it should grab object keys **below** the targeted path segment and generate handler functions for them. 276 | 277 | Because the results of a wildcard path are determined **programmatically** the targeted store member **must exist** when the helper is run! 278 | 279 | For example, at the point of calling `get('user/*')` a module called `user` must exist in the store: 280 | 281 | 282 | ```js 283 | // components/User.js 284 | export default { 285 | computed: { 286 | ...get('user/*') // `store.state.user` module MUST exist at this point 287 | } 288 | } 289 | ``` 290 | 291 | There are two main situations where this may not be the case: 292 | 293 | 1. when routes have been imported before the store 294 | 2. when using dynamically registered modules 295 | 296 | Read below to find out more and how to mitigate these issues. 297 | 298 | #### `Correct router setup` 299 | 300 | It's easy to forget that components are imported when the routes are set up: 301 | 302 | ```js 303 | import User from 'components/User' 304 | export default [ 305 | { path: 'user/:id', component: User } 306 | ] 307 | ``` 308 | 309 | This results in the component definition being loaded **and the code within being run**. If any component helpers have been called with wildcard paths, they'll query the requested state object, and if it **hasn't yet** been added to the store - Pathify will log an error: 310 | 311 | ```console 312 | [Vuex Pathify] Unable to expand wildcard path 'user/*': 313 | - The usual reason for this is that the router was set up before the store 314 | - Make sure the store is imported before the router, then reload 315 | ``` 316 | 317 | To solve this, simply import your store before your router so that properties are set up before being asked for: 318 | 319 | ```js 320 | import store from './store' 321 | import router from './router' 322 | ``` 323 | 324 | If you're using Nuxt and are still getting errors, make sure you haven't loaded any components that use Pathify and wildcards in any files in `/plugins` as these will load before Nuxt has a chance to load Vuex. 325 | 326 | 327 | #### `Dynamic module registration` 328 | 329 | Vuex has the ability to register modules [dynamically](https://vuex.vuejs.org/en/modules.html#dynamic-module-registration) rather than import them all when your project loads. Use cases for this might include: 330 | 331 | - a module that is used only in a subset of routes 332 | - one component and potentially child-components need access to the same store 333 | - a set of components might use copies of the same store 334 | 335 | Let's take the first example, that a `user/` route requires a `user` store module to be loaded: 336 | 337 | ```js 338 | // components/User.js 339 | import user from 'store/user' 340 | 341 | export default { 342 | beforeCreate () { 343 | this.$store.registerModule('user', user) 344 | }, 345 | 346 | computed: get('user/*') // ERROR! `store.state.user` does not exist at this point! 347 | } 348 | ``` 349 | 350 | The problem is that at the time of executing `get()` the `created()` lifecycle hook hasn't run. 351 | 352 | To get round this, there are a few options: 353 | 354 | 1. **don't use wildcards** with dynamic modules 355 | 2. register the module in a **composing** component 356 | 3. register the module in a **global router** `beforeRouteEnter` hook 357 | 4. assign computed properties **programmatically** in the component `beforeCreate` hook 358 | 359 | The first option is workable as it's self-contained and only a little more code. The next two options require additional architecture, so might not be workable. The forth option whilst straightforward, requires a not trivial amount of code to set up, as you need to account for a few edge cases. 360 | 361 | As such, Pathify ships with a `registerModule` helper which you can read about below. 362 | 363 | #### `registerModule()` 364 | 365 | Pathify's `registerModule()` helper is designed to: 366 | 367 | - register a store module via component `beforeCreate` 368 | - add or extend the component's computed properties with new ones 369 | - add or extend the component's methods with new ones 370 | - unregister a store module via component `destroyed` 371 | 372 | Its signature is similar to `Vuex.registerModule()` with the addition of a callback that should return new component blocks. It is implemented as a function which returns an object which can used as a base class or mixin. 373 | 374 | Below is an example using the helper as a base class: 375 | 376 | ```js 377 | // imports 378 | import { get, call, registerModule } from 'vuex-pathify' 379 | import module from './store/user' 380 | 381 | // callback to return lazily-executed Pathify helpers 382 | const members = function () { 383 | return { 384 | computed: get('user/*'), 385 | methods: call('user/*') 386 | } 387 | } 388 | 389 | /** 390 | * User component definition 391 | */ 392 | export default { 393 | // extend from generated base class 394 | extend: registerModule('user', module, members), 395 | 396 | // additional computed properties 397 | computed: { 398 | foo () { ... } 399 | }, 400 | 401 | // additional methods 402 | methods: { 403 | bar () { ... } 404 | } 405 | } 406 | ``` 407 | 408 | If you're interested to see what happens behind the scenes [check out the code](https://github.com/davestewart/vuex-pathify/blob/master/src/helpers/modules.js) on GitHub. 409 | -------------------------------------------------------------------------------- /docs/pages/guide/decorators.md: -------------------------------------------------------------------------------- 1 | # Class component decorators 2 | 3 | > **Optional** component property decorators for **single property access** to be used with class based components 4 | 5 | ## Overview 6 | 7 | For developers who prefer class-based component development, Pathify allows you to generate computed properties using the `@decorator` syntax. 8 | 9 | They are [single-property-access](api/component?id=single-property-access) equivalents of their [component helpers](api/component) counterpart. 10 | 11 | Note that Pathify imports [`vue-class-component`](https://github.com/vuejs/vue-class-component) internally, but this may change in the future. 12 | 13 | ## Usage 14 | 15 | !> **Important syntax difference!** Decorators use Sentence-case, i.e. `Get` and not `get`! 16 | 17 | The following gives an example of some of the main features: 18 | 19 | **ES** 20 | 21 | ```js 22 | import { Get, Sync, Call } from 'vuex-pathify' 23 | 24 | // component 25 | @Component 26 | export default class Basket extends Vue { 27 | @Get('products/items') items 28 | @Sync('products/tax') tax 29 | @Call('products/setDiscount') setDiscount 30 | } 31 | ``` 32 | 33 | **TS** 34 | 35 | ```ts 36 | import { Get, Sync, Call } from 'vuex-pathify' 37 | 38 | // component 39 | @Component 40 | export default class Basket extends Vue { 41 | @Get('products/items') items!: Item[] 42 | @Sync('products/tax') tax!: number 43 | @Call('products/setDiscount') setDiscount!: (rate: number) => any 44 | } 45 | ``` 46 | 47 | Which is equivalent of: 48 | 49 | ```js 50 | import { get, sync, call } from 'vuex-pathify' 51 | 52 | export default { 53 | computed: { 54 | items: get('products/items'), 55 | tax: sync('products/tax'), 56 | }, 57 | 58 | methods: { 59 | setDiscount: call('products/setDiscount') 60 | } 61 | } 62 | ``` 63 | 64 | ## API 65 | 66 | Please see [component helpers' single-property-access API](api/component?id=single-property-access). 67 | -------------------------------------------------------------------------------- /docs/pages/guide/paths.md: -------------------------------------------------------------------------------- 1 | # Path syntax 2 | 3 | > Access the store and its properties with a powerful, declarative path syntax 4 | 5 | ## Overview 6 | 7 | Pathify provides a rich [path syntax](#core-syntax) to access Vuex stores, including: 8 | 9 | - module, property and sub-property access 10 | - variable expansion 11 | - wildcard expansion 12 | 13 | There are some additional [direct syntaxes](#direct-syntax) as well, which are designed to handle customisation around non `get/set` naming: 14 | 15 | - direct access 16 | - direct sync 17 | 18 | 19 | ## Usage 20 | 21 | Paths are used throughout Pathify: 22 | 23 | ```js 24 | // global 25 | store.get('items') 26 | store.set('products/items', items) 27 | 28 | // components 29 | computed: { 30 | search: sync('products/filters@search') 31 | } 32 | ``` 33 | 34 | See the path syntax [demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=api/paths) for an editable, live example. 35 | 36 | 37 | 38 | ## Syntax 39 | 40 | ### Core syntax 41 | 42 | #### `Property access` 43 | 44 | Properties are accessed by simply referencing the state name: 45 | 46 | ```js 47 | // [ ... ] 48 | get('items') 49 | ``` 50 | 51 | #### `Module access` 52 | 53 | Modules are accessed by providing the full path to to the module: 54 | 55 | If the example above was in a module called `products`, it would be accessed like so: 56 | 57 | ```js 58 | // [ ... ] 59 | get('products/items') 60 | ``` 61 | 62 | 63 | #### `Sub-property access` 64 | 65 | Sub-property access requires the `@` character, and allows you to read sub-properties to any depth: 66 | 67 | ```js 68 | // first-level properties 69 | get('filters@search') 70 | ``` 71 | ```js 72 | // nested properties using dot notation 73 | get('filters@sort.key') 74 | ``` 75 | ```js 76 | // array access using dot or bracket notation 77 | get('items@0') 78 | get('items@[0].name') 79 | ``` 80 | 81 | To transparently **write** sub-properties, use the [make.mutations()](/guide/store.md#make-mutations) helper or the [Payload](/guide/properties.md#payload-class) class. 82 | 83 | ```js 84 | set('filters@search', 'blue') 85 | ``` 86 | 87 | See the [sub-property access](/guide/properties.md#sub-property-access) section for more information. 88 | 89 | 90 | ### Variable expansion 91 | 92 | Variable `:notation` allows you to use component properties to dynamically build references to store properties. 93 | 94 | They may only be used in [component helpers](/guide/component.md) but can reference store properties or sub-properties: 95 | 96 | ```js 97 | // dynamically reference a property or sub-property 98 | get('projects/:slug') 99 | get('projects@:slug') 100 | 101 | // dynamically sync a deeply-nested property using object and array notation using multiple variables 102 | sync('clients/:name@project[:index].name') 103 | ``` 104 | 105 | Note the following caveats: 106 | 107 | - only top-level properties may be used as variable names, i.e. `:index` but not `:options.index` 108 | - when getting, only `state` will be referenced; `getters` will be ignored 109 | - when setting, only `mutations` will be referenced; `actions` will be ignored 110 | - you can use array `[:index]` or dot `.index` notation for arrays 111 | 112 | ### Wildcard expansion 113 | 114 | Wildcards `*` allow you to reference multiple properties at once, and are used only in components: 115 | 116 | They don't **return** values like other path references, rather they **generate** a hash of named functions for all properties that they expand to: 117 | 118 | ```js 119 | // generate getters for `items`, `search` and `filters` 120 | computed: { 121 | ...get('products/*') 122 | }, 123 | 124 | // generate methods that dispatch `load` and `update` 125 | methods: { 126 | ...call('products/*') 127 | } 128 | ``` 129 | 130 | 131 | See the [component helpers](/guide/component.md#wildcard-property-access) page for more info. 132 | 133 | 134 | 135 | ### Direct syntax 136 | 137 | Pathify has two syntax types which are used to handle customisation around non get/set naming. 138 | 139 | #### `Direct access` 140 | 141 | Direct access syntax uses a bang `!` to skip mapping and access Vuex members directly: 142 | 143 | ```js 144 | set('update!', items) 145 | ``` 146 | 147 | See the [properties](/guide/properties.md#direct-property-access) page for more info. 148 | 149 | #### `Direct sync` 150 | 151 | Direct sync syntax uses a pipe `|` to specify alternate get and set members in component helpers: 152 | 153 | ```js 154 | computed: { 155 | items: sync('items|update!') 156 | } 157 | ``` 158 | 159 | See the [component helpers](/guide/component.md#sync) page for more info. 160 | -------------------------------------------------------------------------------- /docs/pages/guide/properties.md: -------------------------------------------------------------------------------- 1 | # Advanced property access 2 | 3 | > Detailed information on store property access 4 | 5 | ## Overview 6 | 7 | Pathify's unified path syntax and mapping simplifies property access to Vuex, at the expense of some minor flexibility. 8 | 9 | This section covers: 10 | 11 | - [accessor priority](#accessor-priority) - how Pathify simplifies get / set operations 12 | - [direct property access](#direct-property-access) - how to override mapping and target properties manually 13 | - [sub-property access](#sub-property-access) - how Pathify writes to state sub-properties 14 | - [errors](#errors) - what happens if Pathify fails to map a path to a store member 15 | 16 | 17 | ## Usage 18 | 19 | See the advanced property access [demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=api/properties) for an editable, live example. 20 | 21 | ## Details 22 | 23 | ### Accessor priority 24 | 25 | As outlined in the [intro](/intro/pathify.md) Pathify **automatically** determines whether to get via **state or getters**, or set via **actions or mutations**. This feature is called **Accessor Priority** and results in a significant simplification of Vuex's API. 26 | 27 | The basic premise is this: 28 | 29 | - Vuex has 2 ways to: 30 | - get data; **state** and **getters** 31 | - set data; **mutations** and **actions** 32 | - when accessing a property, say `items`, if found: 33 | - a mapped **getter** will be prioritised over a mapped **state** (as the getter will reference it) 34 | - a mapped **action** will be prioritised over a mapped **mutation** (as the action will call it) 35 | 36 | 37 | This logic and implementation serves several purposes: 38 | 39 | 1. Reduces the decision making process outside of the store; if you want a value, you just **ask for it** 40 | 41 | ```js 42 | // don't care about the implementation, just get/set the value 43 | store.get('items') 44 | store.set('items', data) 45 | ``` 46 | 47 | 2. Reduces awkward syntax juggling in modules: 48 | 49 | ```js 50 | // single, unified format 51 | store.get('products/items') 52 | ``` 53 | ```js 54 | // rather than... 55 | store.state.products.items 56 | store.getters['products/items'] 57 | ``` 58 | 59 | 3. Supports a pattern where the `state` can be the "single source of truth" and the `getter` works as a "transformer" function: 60 | 61 | ```js 62 | state: { 63 | // store item objects... 64 | items: [ {}, {}, ... ], 65 | }, 66 | getters: { 67 | // ...return Item models 68 | items (state) => state.items.map(item => new Item(item)) 69 | } 70 | ``` 71 | 72 | Overall the prioritised approach covers the majority of get/set wiring cases, and enables Pathify's APIs to remain simple. 73 | 74 | 75 | 76 | ### Direct property access 77 | 78 | Pathify's [mapping](/setup/mapping.md) algorithm is designed to **map paths to store members** in a predictable 1:1 manner, for example: 79 | 80 | ```js 81 | get('items') // state.items 82 | set('items', data) // actions.setItems 83 | ``` 84 | 85 | This is great for everyday **get/set** usage, but can't account for more nuanced access like `mutations.INCREMENT_VALUE` or `actions.update()`. To work round this, you can reference store members directly. 86 | 87 | #### `Direct access syntax` 88 | 89 | To **skip** mapping and reference a member directly, append a bang `!` to the property name: 90 | 91 | ```js 92 | // call the `INCREMENT_VALUE` mutation directly 93 | set('INCREMENT_VALUE!') 94 | ``` 95 | ```js 96 | // call the `update()` action, rather than `setItems()` 97 | set('update!', data) 98 | ``` 99 | 100 | Note that even though direct access syntax skips the mapping function, it still respects [accessor priority](/guide/properties.md#accessor-priority). 101 | 102 | 103 | #### `Vuex aliases` 104 | 105 | To skip Pathify entirely when setting data, you can use **Vuex aliases**. 106 | 107 | These are Vuex's own methods, but bound to your project's store, and for convenience, available as imports from Pathify: 108 | 109 | ```js 110 | // import 111 | import { commit, dispatch } from 'vuex-pathify' 112 | 113 | // mutations 114 | commit('INCREMENT_VALUE') 115 | 116 | // actions 117 | dispatch('update', data) 118 | ``` 119 | 120 | #### `Access Vuex directly` 121 | 122 | Finally, you can simply access your store **directly**: 123 | 124 | ```js 125 | // get value 126 | const items = this.$store.state.items 127 | 128 | // set value 129 | this.$store.dispatch('update', data) 130 | ``` 131 | 132 | 133 | ### Sub-property access 134 | 135 | Sub-property **reads** are handled transparently by Pathify's store accessors, whilst sub-property **writes** are handled by the store helper's [make.mutations()](/guide/store.md#make-mutations) method. If your mutations are created using the helper, then sub-property writes will be handled automatically. 136 | 137 | If you've written your own mutations and you're using store accessors or component helpers then you'll need to manually handle the Payload class. 138 | 139 | #### `Payload class` 140 | 141 | The `Payload` class is passed to mutations from Pathify's accessor helpers when a path expression includes sub-property access. The class communicates the sub-property `path` and `value`, as well as encapsulating `update()` functionality, and checking for permission to write or even create sub-properties. 142 | 143 | As mentioned, `make.mutations()` takes care of all sub-property writes automatically, but if you need to do it yourself, here's an example of manually creating a mutation function and what to do with the passed Payload: 144 | 145 | ```js 146 | // store 147 | import { Payload } from 'vuex-pathify' 148 | import _ from 'lodash' 149 | 150 | const state = { 151 | sort: { 152 | key: 'id', 153 | order: 'asc' 154 | } 155 | } 156 | 157 | const mutations = { 158 | // manually-created sort mutator 159 | SET_SORT: (state, payload) => { 160 | // debug 161 | console.log('payload', payload) 162 | 163 | // if we have a Payload, do something with it 164 | if (payload instanceof Payload) { 165 | 166 | // either, update using payload... 167 | state.sort = payload.update(state.sort) 168 | 169 | // ...or, update using dot-notation `path` 170 | _.set(state.sort, payload.path, payload.value) 171 | } 172 | 173 | // otherwise, handle normally 174 | else { 175 | state.sort = payload 176 | } 177 | } 178 | } 179 | ``` 180 | ```js 181 | // global 182 | store.set('sort@order', 'desc') 183 | ``` 184 | 185 | 186 | 187 | ### Errors 188 | 189 | In the event that a supplied path does not map to a property, Pathify will let you know: 190 | 191 | ```js 192 | // would map to `mutations.SET_FOO` or `actions.setFoo` 193 | store.set('foo', false) 194 | ``` 195 | ```text 196 | [Vuex Pathify] Unable to map path 'foo': 197 | - Did not find action 'setFoo' or mutation 'SET_FOO' on store 198 | - Use path 'foo!' to target store member directly 199 | ``` 200 | 201 | As a developer you can update the path or use any of the direct access methods above. 202 | -------------------------------------------------------------------------------- /docs/pages/guide/store.md: -------------------------------------------------------------------------------- 1 | # Store helpers 2 | 3 | 4 | > Store helpers eliminate store boilerplate 5 | 6 | ## Overview 7 | 8 | Store helpers **eliminate boilerplate** by creating redundant 1:1 wiring functions from a supplied state object. 9 | 10 | They are implemented as **helper functions** which: 11 | 12 | - can include only certain store members 13 | - can be mixed in with additional declarations 14 | 15 | Each helper builds and returns the appropriate JavaScript functions. 16 | 17 | 18 | ## Usage 19 | 20 | The following example illustrates functionality and usage for all the helper functions: 21 | 22 | ```js 23 | import Api from 'services/Api' 24 | import { make } from 'vuex-pathify' 25 | 26 | const state = { 27 | items: [], 28 | status: '', 29 | filters: { 30 | search: '', 31 | sort : { ... } // object sub-properties 32 | } 33 | } 34 | 35 | // make all mutations 36 | const mutations = make.mutations(state) 37 | 38 | const actions = { 39 | // automatically create only `setItems()` action 40 | ...make.actions('items'), 41 | 42 | // manually add load items action 43 | loadItems({ dispatch }) { 44 | Api.get('items').then(data => dispatch('setItems', data)) 45 | }, 46 | } 47 | 48 | const getters = { 49 | // make all getters (optional) 50 | ...make.getters(state), 51 | 52 | // overwrite default `items` getter 53 | items: state => { 54 | return state.items.map(item => new Item(item)) 55 | }, 56 | 57 | // add new `filteredItems` getter 58 | filteredItems: (state, getters) => { 59 | return getters.items.filter(item => item.title.includes(state.filters.search)) 60 | } 61 | } 62 | 63 | export default { 64 | // namespaced: true, // add this if in module 65 | state, 66 | mutations, 67 | actions, 68 | getters, 69 | } 70 | ``` 71 | !> Note that the code specifically demonstrates a **getters/action-heavy store** approach in order to show full usage of the `make.*` helpers. However, the Pathify-recommended approach is to **eschew** redundant getters and actions (those which simply proxy work to state and mutations) and only create mutations, letting Pathify do the heavy-lifting for you. 72 | 73 | See the store helpers [demo](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=api/store) for an editable, live example. 74 | 75 | ## API 76 | 77 | ### Helpers 78 | 79 | #### `make.mutations(state: Object | Array | String | Function): Object` 80 | 81 | Use `make.mutations()` to generate default mutations for your state object: 82 | 83 | ```js 84 | const mutations = make.mutations(state) 85 | ``` 86 | 87 | The helper generates the following code: 88 | 89 | ```js 90 | // `key` is a scoped variable unique to each created function 91 | mutations = { 92 | SET_ITEMS: (state, value) => state.items = value instanceof Payload 93 | ? value.update(state[key]) 94 | : value, 95 | SET_STATUS: (state, value) => state.search = value instanceof Payload 96 | ? value.update(state[key]) 97 | : value, 98 | SET_FILTERS: (state, value) => state.filters = value instanceof Payload 99 | ? value.update(state[key]) 100 | : value, 101 | } 102 | ``` 103 | 104 | Note transparent support for [sub-property writes](/guide/properties#sub-property-access) thanks to the Payload class. 105 | 106 | 107 | #### `make.actions(state: Object | Array | String | Function): Object` 108 | 109 | You can use `make.actions()` to generate default actions for your state object: 110 | 111 | ```js 112 | const actions = make.actions(state) 113 | ``` 114 | 115 | The helper generates the following code: 116 | 117 | ```js 118 | const actions = { 119 | setItems: ({commit}, value) => commit('SET_ITEMS', value), 120 | setStatus: ({commit}, value) => commit('SET_STATUS', value), 121 | setFilters: ({commit}, value) => commit('SET_FILTERS', value), 122 | } 123 | ``` 124 | 125 | If using Pathify as your core store access mechanism, you generally don't need to create redundant actions, but if you're the kind of developer who prefers accessing the store by **actions only**, this will save you writing them all yourself. 126 | 127 | 128 | #### `make.getters(state: Object | Array | String | Function): Object` 129 | 130 | You can use `make.getters()` to generate default getters for your state object: 131 | 132 | ```js 133 | const getters = make.getters(state) 134 | ``` 135 | 136 | The helper generates the following code: 137 | 138 | ```js 139 | const getters = { 140 | items: state => state.items, 141 | status: state => state.search, 142 | filters: state => state.filters, 143 | } 144 | ``` 145 | 146 | If using Pathify as your core store access mechanism, you generally don't need to create redundant getters, but if you're the kind of developer who prefers accessing the store by **getters only**, this will save you writing them all yourself. 147 | 148 | 149 | ### Partial generation 150 | 151 | The `make.*` helpers take a variety of argument types, with the expectation to pass in an existing `state` object and create helpers for **all** properties: 152 | 153 | ```js 154 | const state = { ... } 155 | const mutations = make.mutations(state) 156 | ``` 157 | 158 | However, you can generate only **some** properties by passing alternative parameters: 159 | 160 | 161 | ```js 162 | // strings have properties parsed from them 163 | const mutations = make.mutations('items status') 164 | ``` 165 | ```js 166 | // arrays use the passed values 167 | const mutations = make.mutations(['items', 'status']) 168 | ``` 169 | ```js 170 | // objects use the passed keys 171 | const mutations = make.mutations({items: true, status: true}) 172 | ``` 173 | ```js 174 | // functions will be executed; any of the above types can be returned 175 | function state () { ... } 176 | const mutations = make.mutations(state) 177 | ``` 178 | -------------------------------------------------------------------------------- /docs/pages/home.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Vuex Pathify 4 | 5 | > Pathify provides a declarative, state-based, path interface to your Vuex store 6 | 7 | With Pathify, you access Vuex by **path**: 8 | 9 | ```pathify 10 | 'foo/bar@a.b.c' 11 | ``` 12 | 13 | Paths can reference any **module**, **property** or **sub-property**: 14 | 15 | ![pathify-diagram](../assets/img/readme/pathify-diagram.png) 16 | 17 | 18 | **Get** or **set** data without **syntax juggling** or worrying about **implementation**: 19 | 20 | ```pathify 21 | store.get('loaded') 22 | ``` 23 | ```pathify 24 | store.set('loaded', true) 25 | ``` 26 | 27 | Reach into **sub-properties** and **arrays**: 28 | 29 | ```pathify 30 | store.get('products@items.0.name') 31 | store.set('products@items.1.name', 'Vuex Pathify') 32 | ``` 33 | 34 | Set up **one or two-way** data binding on **any** store value without **bloat** or **fuss**: 35 | 36 | ```pathify 37 | computed: { 38 | products: get('products'), 39 | category: sync('filters@category') 40 | } 41 | ``` 42 | 43 | Wire **multiple** properties (or sub-properties) using **array**, **object** and **wildcard** formats: 44 | 45 | ```pathify 46 | computed: { 47 | ...sync('filters@sort', [ 48 | 'order', 49 | 'key' 50 | ]), 51 | 52 | ...sync('filters@sort', { 53 | sortOrder: 'order', 54 | sortKey: 'key' 55 | }), 56 | 57 | ...sync('filters@sort.*') 58 | } 59 | ``` 60 | 61 | 62 | Use **variable expansion** to dynamically reference store properties: 63 | 64 | ```pathify 65 | computed: { 66 | product: get('products@items:index') 67 | } 68 | ``` 69 | 70 | 71 | Set up mutations – **including sub-property mutations** – in a single line: 72 | 73 | ```pathify 74 | make.mutations(state) 75 | ``` 76 | 77 | And that's it! 78 | 79 | In practical terms, Pathify results in: 80 | 81 | - less cognitive overhead 82 | - zero store boilerplate 83 | - one-liner wiring 84 | - cleaner code 85 | - lighter files 86 | 87 | ### Next steps 88 | 89 | To get started: 90 | 91 | - visit the [Installation](/setup/install.md) page to install and use Pathify now 92 | - read the [Guide](/guide/paths.md) for a deep dive into Pathify's features 93 | - read the [API](/reference/api.md) to see just the code 94 | 95 | To see Pathify in action: 96 | 97 | - check the editable CodeSandbox [demos](/intro/demos.md) 98 | 99 | For a deeper insight: 100 | 101 | - read the [Intro](/intro/pathify.md) for an overview of the Pathify mechanism 102 | - check out the [code comparison](https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main?initialpath=code) demos which illustrate reductions in Vuex code of between **2 and 14 times** 103 | -------------------------------------------------------------------------------- /docs/pages/intro/demos.md: -------------------------------------------------------------------------------- 1 | # Demos 2 | 3 | > See Pathify in action 4 | 5 | Playing with the code is often the quickest way to see how a library works, so Pathify has a bunch of editable demos. 6 | 7 | Demos can viewed and edited [online](https://codesandbox.io/s/github/davestewart/vuex-pathify/tree/master/demo) or [downloaded](https://github.com/davestewart/vuex-pathify-demos) and run locally. 8 | 9 | 10 | ## Simple demo 11 | 12 | This demo shows a basic, 2-property store setup with Pathify component wiring. 13 | 14 | [![simple demo](../../assets/img/demo/demo-simple.png)][simple] 15 | 16 | The demo is completely self-contained and is in effect a Pathify starter template. 17 | 18 | To load the simple demo, click [here][simple]. 19 | 20 | ## Main demo 21 | 22 | This set of demos contains a range of components + store setups illustrating both API and real-world usage: 23 | 24 | [![main demo](../../assets/img/demo/demo-feature.png)][main] 25 | 26 | Use the navigation to load the demos, then click the green buttons at the top to edit the associated files: 27 | 28 | ![demo help](../../assets/img/demo/demo-help.png) 29 | 30 | To load the main demo, click [here][main]. 31 | 32 | ## Nuxt demo 33 | 34 | Finally, there is a [Nuxt](https://nuxtjs.org/) demo, to ensure that Pathify works correctly on the server side. 35 | 36 | Rather than being viewable in a browser, the Nuxt demo must be downloaded, installed and run locally. 37 | 38 | To view the repository, click [here](https://github.com/davestewart/vuex-pathify-demos/tree/master/nuxt). 39 | 40 | [simple]: https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/simple?module=%2Fsrc%2Fcomponents%2FHelloWorld.vue 41 | [main]: https://codesandbox.io/s/github/davestewart/vuex-pathify-demos/tree/master/main 42 | -------------------------------------------------------------------------------- /docs/pages/intro/pathify.md: -------------------------------------------------------------------------------- 1 | 2 | # Pathify 101 3 | 4 | > How Pathify does what it does 5 | 6 | Pathify's aim is to **simplify** the overall Vuex development experience. 7 | 8 | Its core mechanism is a custom [path syntax](/guide/paths.md) which can reference any state property: 9 | 10 | ```js 11 | 'products/items@filters.search' 12 | ``` 13 | 14 | Paths are [mapped](/setup/mapping.md) to store members via a configurable naming scheme: 15 | 16 | ```js 17 | Operation Member Name Scheme 18 | 19 | read state foo // base name 20 | read getters foo // no prefix, no case conversion 21 | write mutations SET_FOO // "set" prefix, constant case, 22 | write actions setFoo // "set" prefix, camel case, 23 | ``` 24 | 25 | Store [access](/guide/accessors.md) and component [wiring](/guide/component.md) are unified with `get()`, `set()`, `sync()` and `call()` methods: 26 | 27 | ```js 28 | // global 29 | const items = store.get('products/items') 30 | store.set('products/items', items) 31 | 32 | // component 33 | computed: { 34 | total: get('products/total'), 35 | items: sync('products/items') 36 | }, 37 | 38 | methods: { 39 | submit: call('products/submit') 40 | } 41 | ``` 42 | 43 | Implementation details are simplified by [prioritising](/guide/properties.md) **getters over state** and **actions over mutations**: 44 | 45 | ```js 46 | Pathify Vuex 47 | 48 | store.get('products/items') <- store.getters['products/items'] 49 | store.state.products.items 50 | store.set('products/items', items) -> dispatch('products/setItems', items) 51 | commit('products/SET_ITEMS', items) 52 | ``` 53 | 54 | 55 | The overall approach results in a significant simplification of Vuex's API: 56 | 57 | - from **4** operations, **4** helpers, **3** accessor syntaxes and **3** (or sometimes **4**) naming formats 58 | - to **4** methods and **1** path format 59 | 60 | For store members that don't fit the "set/get" paradigm, there are several [direct access](/guide/properties.md#direct-property-access) mechanisms available. 61 | 62 | Finally, [store helpers](/guide/store.md) provide transparent sub-property access whilst **eliminating** store setup boilerplate: 63 | 64 | ```js 65 | const mutations = make.mutations(state) 66 | ``` 67 | 68 | 69 | The end result a **radical** reduction in store setup, component wiring, lines of code and cognitive overhead, leaving you more time and bandwidth to build your application. 70 | -------------------------------------------------------------------------------- /docs/pages/reference/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | > Pathify's full API 4 | 5 | Whilst the guide deep-dives to explain how Pathify works, this section is just a high-level reference to the code: 6 | 7 | - [Store Helpers](#store-helpers)
8 | Automatically generate mutations, getters, or actions from state 9 | - [Store Accessors](#store-accessors)
10 | Get, set and copy data directly on the store 11 | - [Component Helpers](#component-helpers)
12 | Get, sync or call the vuex store from components 13 | - [Class Component Decorators](#class-component-decorators)
14 | Get, sync or call the vuex store from class components 15 | - [Vuex Helpers](#vuex-helpers)
16 | Pathify versions of commit, dispatch or registerModule 17 | 18 | 19 | ## Store Helpers 20 | 21 | > See the [guide](/guide/store) for more detailed information. 22 | 23 | ### `make.*` 24 | 25 | Automatically generate `mutations`, `getters`, or `actions`: 26 | 27 | ```js 28 | make.*(state: object | function | string[] | string) 29 | ``` 30 | 31 | The `state` parameter can be any of the following: 32 | 33 | - your existing `state` (either an `object` or `function` returning an `object`) 34 | - an `object` of keys 35 | - an `array` of `string`s 36 | - a space-separated `string` of keys 37 | 38 | 39 | For example: 40 | 41 | ```js 42 | import { make } from 'vuex-pathify' 43 | 44 | const state = { a:1, b: 2 } 45 | const mutations = make.mutations(state) 46 | const actions = make.actions(state) 47 | const getters = make.getters(state) 48 | 49 | export default { state, mutations, actions, getters } 50 | ``` 51 | 52 | Each helper will return a hash of functions you can use directly or mix into exising Vuex members, i.e.: 53 | 54 | ```js 55 | const mutations = { 56 | ...make.mutations(state), 57 | addToFoo: (state, value) => state.foo += value 58 | } 59 | ``` 60 | 61 | ### `Payload` 62 | 63 | The `Payload` class is used internally to signify to Pathify that sub-properties may be written to: 64 | 65 | ```js 66 | new Payload(expr: string, path: string, value: *) 67 | ``` 68 | 69 | Generally you won't use it directly, but you can instantiate it if you need to: 70 | 71 | ```js 72 | import { Payload } from 'vuex-pathify' 73 | store.commit('filters', new Payload('filters', 'sort.order', 'asc')) 74 | ``` 75 | 76 | ## Store Accessors 77 | 78 | > See the [guide](/guide/accessors) for more detailed information. 79 | 80 | Directly `get`, `set` and `copy` values on the root store instance. 81 | 82 | ### `store.get()` 83 | 84 | Get a value directly from the store: 85 | 86 | ```js 87 | store.get(path: string): * 88 | ``` 89 | 90 | For example: 91 | 92 | ```js 93 | const order = store.get('products/filters@sort.order') 94 | ``` 95 | 96 | ### `store.set()` 97 | 98 | Set a value directly on the store: 99 | 100 | ```js 101 | store.set(path: string, value: any): void 102 | ``` 103 | 104 | For example: 105 | 106 | ```js 107 | store.get('products/filters@sort.order', 'asc') 108 | ``` 109 | 110 | ### `store.copy()` 111 | 112 | Copy a value directly from the store: 113 | 114 | ```js 115 | store.copy(path: string): * 116 | ``` 117 | 118 | For example: 119 | 120 | ```js 121 | const sort = store.copy('products/filters@sort') 122 | ``` 123 | 124 | ## Component Helpers 125 | 126 | > See the [guide](/guide/component) for more detailed information. 127 | 128 | Wire component properties directly to the store. 129 | 130 | ### `component get()` 131 | 132 | Wire one or more one-way (read) store properties to a component: 133 | 134 | ```js 135 | get(path: string, string[] | Record): * 136 | ``` 137 | 138 | For example: 139 | 140 | ```js 141 | import { get } from 'vuex-pathify' 142 | export default { 143 | computed: { 144 | items: get('products/items'), 145 | ...get('products/filters@sort', ['order', 'key']), 146 | } 147 | } 148 | ``` 149 | 150 | ### `component sync()` 151 | 152 | Wire one or more two-way (read/write) store property to a component: 153 | 154 | ```js 155 | sync(path: string, string[] | Record) 156 | ``` 157 | 158 | For example: 159 | 160 | ```js 161 | import { sync } from 'vuex-pathify' 162 | export default { 163 | computed: { 164 | search: sync('products/search'), 165 | ...sync('products/filters@sort', ['order', 'key']), 166 | } 167 | } 168 | ``` 169 | 170 | ### `component call()` 171 | 172 | Wire one or more store actions to a component: 173 | 174 | ```js 175 | call(path: string, string[] | Record) 176 | ``` 177 | 178 | For example: 179 | 180 | ```js 181 | import { call } from 'vuex-pathify' 182 | export default { 183 | methods: { 184 | load: call('products/load'), 185 | ...call('products', ['update']), 186 | } 187 | } 188 | ``` 189 | 190 | ## Class Component Decorators 191 | 192 | > See the [guide](/guide/decorators) for more detailed information. 193 | 194 | Single property access to be used with class based components. 195 | 196 | 197 | ### `class @Get()` 198 | 199 | Wire a single one-way (read) store properties to a component: 200 | 201 | ```js 202 | @Get(path: string): * 203 | ``` 204 | 205 | For example: 206 | 207 | ```js 208 | import { Get } from 'vuex-pathify' 209 | 210 | @Component 211 | export default class Basket extends Vue { 212 | @Get('products/items') items 213 | } 214 | ``` 215 | ```ts 216 | import { Get } from 'vuex-pathify' 217 | 218 | @Component 219 | export default class Basket extends Vue { 220 | @Get('products/items') items!: Item[] 221 | } 222 | ``` 223 | 224 | ### `class @Sync()` 225 | 226 | Wire a single two-way (read/write) store property to a component: 227 | 228 | ```js 229 | @Sync(path: string) 230 | ``` 231 | 232 | For example: 233 | 234 | ```js 235 | import { Sync } from 'vuex-pathify' 236 | 237 | @Component 238 | export default class Basket extends Vue { 239 | @Sync('products/tax') tax 240 | } 241 | ``` 242 | ```ts 243 | import { Sync } from 'vuex-pathify' 244 | 245 | @Component 246 | export default class Basket extends Vue { 247 | @Sync('products/tax') tax!: number 248 | } 249 | ``` 250 | ### `class @Call()` 251 | 252 | Wire a single store actions to a component: 253 | 254 | ```js 255 | @Call(path: string) 256 | ``` 257 | 258 | For example: 259 | 260 | ```js 261 | import { Call } from 'vuex-pathify' 262 | 263 | @Component 264 | export default class Basket extends Vue { 265 | @Call('products/setDiscount') setDiscount 266 | } 267 | ``` 268 | ```ts 269 | import { Call } from 'vuex-pathify' 270 | 271 | @Component 272 | export default class Basket extends Vue { 273 | @Call('products/setDiscount') setDiscount!: (rate: number) => any 274 | } 275 | ``` 276 | 277 | ## Vuex Helpers 278 | 279 | Pathify exports various Vuex functions along with its own to make your imports a little cleaner. 280 | 281 | ### `commit()` 282 | 283 | An alias to the Vuex `commit` method, so you can commit directly to the store: 284 | 285 | ```js 286 | commit(path: string, value: *) 287 | ``` 288 | 289 | For example: 290 | 291 | ```js 292 | import { commit } from 'vuex-pathify' 293 | 294 | export default { 295 | methods: { 296 | addItem (item) { 297 | commit('products/item', item) 298 | } 299 | } 300 | } 301 | ``` 302 | 303 | ### `dispatch()` 304 | 305 | An alias to the Vuex `dispatch` method, so you can dispatch directly to the store: 306 | 307 | ```js 308 | dispatch(path: string, payload: *) 309 | ``` 310 | 311 | 312 | For example: 313 | 314 | ```js 315 | import { dispatch } from 'vuex-pathify' 316 | 317 | export default { 318 | methods: { 319 | addItem (item) { 320 | dispatch('products/addItem', item) 321 | } 322 | } 323 | } 324 | ``` 325 | 326 | ### `registerModule()` 327 | 328 | Dynamically registers a new Vuex module during component creation: 329 | 330 | ```js 331 | registerModule(path: string, module: Object, members: Function, options: Object) 332 | ``` 333 | 334 | For example: 335 | 336 | ```js 337 | // imports 338 | import { get, call, registerModule } from 'vuex-pathify' 339 | import module from './store/user' 340 | 341 | // callback to return lazily-executed Pathify helpers 342 | const members = function () { 343 | return { 344 | computed: get('user/*'), 345 | methods: call('user/*') 346 | } 347 | } 348 | 349 | /** 350 | * User component definition 351 | */ 352 | export default { 353 | // extend from generated base class 354 | extend: registerModule('user', module, members), 355 | } 356 | ``` 357 | 358 | For full usage, see [Dynamic module registration](/guide/component?id=registermodule). 359 | -------------------------------------------------------------------------------- /docs/pages/reference/code.md: -------------------------------------------------------------------------------- 1 | # Example code 2 | 3 | > Example code used in the documentation 4 | 5 | ## Overview 6 | 7 | Throughout the API docs, you'll see various code snippets, often referring to "products", "items", "filters", etc. 8 | 9 | They are based on the following example, sometimes as a root store, other times as a module: 10 | 11 | 12 | [filename](products.js ':include :type=code') 13 | -------------------------------------------------------------------------------- /docs/pages/reference/products.js: -------------------------------------------------------------------------------- 1 | // pathify 2 | import { make } from 'vuex-pathify' 3 | 4 | // local files 5 | import { sort } from '../utils' 6 | import Api from '../services/Api' 7 | import Item from '../classes/Item' 8 | 9 | // base state 10 | const state = { 11 | items: [], 12 | status: '', 13 | filters: { 14 | search: '', 15 | sort: { 16 | order: 'asc', 17 | key: 'id', 18 | } 19 | } 20 | } 21 | 22 | // getter overrides 23 | const getters = { 24 | // return Item instances 25 | items: (state) => state.items.map(data => new Item(data)), 26 | 27 | // getter value 28 | filteredItems (state, getters) { 29 | return getters.items 30 | .filter(item => item.title.includes(state.filters.search)) 31 | .sort(sort(state.filters.sort)) 32 | }, 33 | 34 | // custom getter function 35 | filterBy (state, getters) { 36 | return function (key, value) { 37 | return getters.items 38 | .filter(item => item[key] === value) 39 | .sort(sort(state.filters.sort)) 40 | } 41 | } 42 | } 43 | 44 | // automatically generate mutations 45 | const mutations = make.mutations (state) 46 | 47 | // manually-created actions 48 | const actions = { 49 | // load items 50 | load ({ commit }) { 51 | commit('SET_STATUS', 'loading') 52 | Api 53 | .get('items') 54 | .then(data => { 55 | commit('SET_STATUS', '') 56 | commit('SET_ITEMS', data) 57 | }) 58 | .catch(err => commit('SET_STATUS', err.message)) 59 | }, 60 | 61 | // manually update items 62 | update ({commit}, items) { 63 | // convert input to raw data 64 | const data = items.map(item => Object.assign({}, item)) 65 | commit('SET_ITEMS', data) 66 | } 67 | } 68 | 69 | // export store 70 | export default { 71 | namespaced: true, 72 | state, 73 | getters, 74 | mutations, 75 | actions, 76 | } 77 | -------------------------------------------------------------------------------- /docs/pages/setup/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | > Configure Pathify using custom options 4 | 5 | ## Overview 6 | 7 | Pathify installs with the following defaults: 8 | 9 | 10 | ```js 11 | export default { 12 | mapping: 'standard', // map states to store members using the "standard" scheme 13 | strict: true, // throw an error if the store member cannot be found 14 | cache: true, // cache generated functions for faster re-use 15 | deep: true, // allow sub-property access to Vuex stores 16 | } 17 | ``` 18 | 19 | If you're a new user, and you need to configure a mapping scheme, visit the [mapping](/setup/mapping.md) page first. 20 | 21 | If you just need to tweak these values, review the [options](/setup/options.md) for more information. 22 | 23 | When you're ready to save your changes, **follow the steps below**. 24 | 25 | ## Config 26 | 27 |

28 | Important!
29 | Because of the way ES6 imports work, configuration must be saved in a standalone file and must be imported before any store files. 30 |

31 | 32 | Create a new file called `pathify.js` and save it in the same folder as your store index file (or anywhere, really). 33 | 34 | Add the following code to import, configure and re-export the Pathify, modifying the [options](/setup/options.md) as desired: 35 | 36 | ```js 37 | import pathify from 'vuex-pathify' 38 | export default pathify 39 | 40 | // options 41 | pathify.options.mapping = 'simple' 42 | pathify.options.deep = false 43 | ``` 44 | 45 | In your store's index file **make sure to import the local config file** rather than the package: 46 | 47 | ```js 48 | // packages 49 | import Vue from 'vue' 50 | import Vuex from 'vuex' 51 | 52 | // pathify config 53 | import pathify from './pathify' // <-- note the ./ denoting a local file! 54 | 55 | // store 56 | const store = { 57 | ... 58 | plugins: [ pathify.plugin ], 59 | ... 60 | } 61 | ``` 62 | 63 | Then finish setting up your project as you would otherwise. 64 | 65 |

Remember:
Failing to import Pathify configuration before store imports will result in the default settings being used. This will result in a broken app and lots of console errors as mutation and action identifiers will be wrong.

66 | 67 | ## Troubleshooting 68 | 69 | If you need to check your settings, you can call `pathify.debug()` at any time which will output the current `options` values and a breakdown of the mapping function output. 70 | 71 | ```text 72 | [Vuex Pathify] Options: 73 | 74 | Mapping (standard) 75 | ------------------------------- 76 | path : value 77 | state : value 78 | getters : value 79 | actions : setValue 80 | mutations : SET_VALUE 81 | 82 | Settings 83 | ------------------------------- 84 | strict : true 85 | cache : true 86 | deep : true 87 | ``` 88 | 89 | Note that calling `debug()` **will not** not show you the configuration used by your store files if you failed to import any custom config before the store files were accessed! -------------------------------------------------------------------------------- /docs/pages/setup/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | > Install and setup the plugin in a new project 4 | 5 | ## Download 6 | 7 | In your project's root folder, download and install the package using npm or yarn: 8 | 9 | ```shell 10 | npm i vuex-pathify 11 | ``` 12 | ```shell 13 | yarn add vuex-pathify 14 | ``` 15 | 16 | ## Config 17 | 18 | If you haven't yet read the [Intro](/intro/pathify.md) section, Pathify **gets and sets** values by mapping **paths** to **store members**. 19 | 20 | To do this, Pathify needs to know your store **naming scheme** so it can generate code that suits your setup: 21 | 22 | scheme|path|state|getter|mutation|action|notes 23 | :---|:---|:---|:---|:---|:---|:--- 24 | **standard**|`/foo`|foo|foo|SET_FOO|setFoo|Used by most Vue developers 25 | **simple**|`/foo`|foo|foo|foo|setFoo|Simpler, unified format 26 | **custom**|`/foo`|?|?|?|?|User must supply custom mapping function 27 | 28 | If you use the default **standard** naming scheme above, then no configuration is required. 29 | 30 | If not, you'll need to [configure](/setup/config.md) Pathify before continuing. 31 | 32 | ## Setup 33 | 34 | In your store's entry point, setup your store, and activate the plugin: 35 | 36 | ```js 37 | // packages 38 | import Vue from 'vue' 39 | import Vuex from 'vuex' 40 | import pathify from 'vuex-pathify' 41 | 42 | // store definition 43 | const store = { 44 | // state, members, modules, etc 45 | } 46 | 47 | // store 48 | Vue.use(Vuex) 49 | export default new Vuex.Store({ 50 | plugins: [ pathify.plugin ], // activate plugin 51 | ...store 52 | }) 53 | ``` 54 | 55 | To get going immediately after installing, check out: 56 | 57 | - [Guide](/guide/paths.md) 58 | - [API](/guide/paths.md) 59 | - [Demos](/intro/demos.md) 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/pages/setup/mapping.md: -------------------------------------------------------------------------------- 1 | # Mapping 2 | 3 | > Configure paths to map to store members 4 | 5 | ## Overview 6 | 7 | Pathify's algorithm maps **paths** to **store members**, such as `foo/` to `SET_FOO` or similar. 8 | 9 | It can be configured using a preset or custom function. 10 | 11 | 14 | 15 | ## Presets 16 | 17 | There are two mapping presets to choose from, which map as follows: 18 | 19 | name|path|state|getter|mutation|action|notes 20 | :---|:---|:---|:---|:---|:---|:--- 21 | **standard**|`/foo`|foo|foo|SET_FOO|setFoo|Used by most Vue developers 22 | **simple**|`/foo`|foo|foo|foo|setFoo|Simpler, unified format 23 | 24 | 25 | To reconfigure Pathify from the default `standard` mapping, set Pathify's options like so: 26 | 27 | ```js 28 | pathify.options.mapping = 'simple' 29 | ``` 30 | 31 | ## Custom function 32 | 33 | If your naming scheme isn't covered by a mapping preset, you can supply a custom mapping function. 34 | 35 | A mapping function at its simplest is just a `switch/case` with some concatenation and formatting: 36 | 37 | ```js 38 | function (type, name, formatters) { 39 | switch(type) { 40 | case 'mutations': 41 | return formatters.const('set', name) // SET_FOO 42 | case 'actions': 43 | return formatters.camel('set', name) // setFoo 44 | case 'getters': 45 | return formatters.camel('get', name) // getFoo 46 | } 47 | return name // foo 48 | } 49 | ``` 50 | 51 | The function **must** return a string which **must** correctly reference store members. 52 | 53 | The function is called with the following parameters: 54 | 55 | - **type** `{string}` - The member type, i.e `state`, `getters`, `mutations`, or `actions` 56 | - **name** `{string}` - The name of the property being targeted, i.e. `foo` 57 | - **formatters** `{object}` - A hash of common formatting functions, `camel`, `snake`, `const` 58 | 59 | You assign it to Pathify's options like so: 60 | 61 | ```js 62 | pathify.options.mapping = function (type, name, formatters) { 63 | // your custom mapping 64 | } 65 | ``` 66 | 67 | ### Formatters 68 | 69 | The formatter functions are passed for convenience as the 3rd parameter and have two roles: 70 | 71 | 1. concatenate separate words 72 | 2. convert their case 73 | 74 | There are 3 functions available: 75 | 76 | - `camel` - format as `camelCase` 77 | - `snake` - format as `snake_case` 78 | - `const` - format as `CONST_CASE` 79 | 80 | As an example: 81 | 82 | ```js 83 | formatters.const('set', 'items') // SET_ITEMS 84 | ``` 85 | 86 | You're free to use the built-in formatters, or use 3rd party helpers like [lodash](https://lodash.com/docs/4.17.5#camelCase). -------------------------------------------------------------------------------- /docs/pages/setup/options.md: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | > Configure Pathify's options 4 | 5 | #### `mapping` 6 | 7 | - Type: String | Function 8 | - Default: "standard" 9 | 10 | The **mapping** option helps determine how Pathify should map Pathify operations to Vuex store members. 11 | 12 | You can choose from a couple of common presets, or provide a custom function. 13 | 14 | See the [mapping](/setup/mapping.md) page for more details. 15 | 16 | #### `deep` 17 | 18 | - Type: Number 19 | - Default: 1 20 | 21 | The **deep** option permits sub-property read/write and even creation for store members of the `Object` type: 22 | 23 | ```js 24 | store.set('sort@order', 'asc') 25 | ``` 26 | 27 | The options are: 28 | 29 | - `0` - disable access to sub-properties 30 | - `1` - enable access to existing sub-properties 31 | - `2` - enable creation of new sub-properties 32 | 33 | If sub-property creation is enabled, new sub-properties can be created on the fly via both `store.set()` and `sync()`. 34 | 35 | Attempting to access or create sub-properties without permission will fail and will generate a console error in development. 36 | 37 | 38 | #### `strict` 39 | 40 | > Not implemented yet 41 | 42 | - Type: Boolean 43 | - Default: true 44 | 45 | The **strict** option causes an error to be thrown if attempting to access to properties that don't exist. 46 | 47 | 48 | 49 | #### `cache` 50 | 51 | > Not implemented yet 52 | 53 | - Type: Boolean 54 | - Default: true 55 | 56 | The **cache** option enables caching of mapping results, making for speedier lookups when paths are accessed or computed properties are recreated. 57 | 58 | Disabling caching has a negligible performance impact. 59 | -------------------------------------------------------------------------------- /docs/pages/sidebar.md: -------------------------------------------------------------------------------- 1 | - Intro 2 | 3 | - [Pathify 101](/intro/pathify.md) 4 | - [Demos](/intro/demos.md) 5 | 6 | - Setup 7 | 8 | - [Installation](/setup/install.md) 9 | - [Configuration](/setup/config.md) 10 | - [Options](/setup/options.md) 11 | - [Mapping](/setup/mapping.md) 12 | 13 | - Guide 14 | 15 | - [Path syntax](/guide/paths.md) 16 | - [Store helpers](/guide/store.md) 17 | - [Store accessors](/guide/accessors.md) 18 | - [Component helpers](/guide/component.md) 19 | - [Class component decorators](/guide/decorators.md) 20 | - [Advanced property access](/guide/properties.md) 21 | 22 | - Reference 23 | 24 | - [API](/reference/api.md) 25 | - [Example code](/reference/code.md) 26 | 27 | - Discussion 28 | 29 | - [Rationale](/discussion/rationale.md) 30 | - [FAQs](/discussion/faq.md) 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-pathify", 3 | "version": "1.5.1", 4 | "description": "Ridiculously simple Vuex setup + wiring", 5 | "main": "dist/vuex-pathify.js", 6 | "module": "dist/vuex-pathify.esm.js", 7 | "jsnext:main": "dist/vuex-pathify.esm.js", 8 | "typings": "types/index.d.ts", 9 | "files": [ 10 | "dist/*", 11 | "types/*.d.ts" 12 | ], 13 | "scripts": { 14 | "dev": "rollup -c build/rollup.js -w", 15 | "build": "rollup -c build/rollup.js", 16 | "docs": "node-sass docs/assets/scss -o docs/assets/css -w docs/assets/scss/**/*.scss & docsify serve docs", 17 | "test": "jest tests/ --verbose", 18 | "test:types": "tsc -p types/test" 19 | }, 20 | "author": "Dave Stewart ", 21 | "homepage": "https://github.com/davestewart/vuex-pathify#readme", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/davestewart/vuex-pathify.git" 25 | }, 26 | "keywords": [ 27 | "vuex", 28 | "store" 29 | ], 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/davestewart/vuex-pathify/issues" 33 | }, 34 | "dependencies": { 35 | "vue-class-component": "^7.2.6" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.12.9", 39 | "@babel/preset-env": "^7.12.7", 40 | "babel-jest": "^24.9.0", 41 | "docsify-cli": "^4.4.4", 42 | "install": "^0.12.2", 43 | "jest": "^24.9.0", 44 | "node-sass": "^7.0.1", 45 | "npm": "^6.14.9", 46 | "rollup": "^0.67.4", 47 | "rollup-plugin-buble": "^0.19.8", 48 | "rollup-plugin-commonjs": "^8.4.1", 49 | "rollup-plugin-license": "^0.12.1", 50 | "rollup-plugin-uglify": "^3.0.0", 51 | "typescript": "^3.9.7", 52 | "vue": "^2.6.12", 53 | "vuex": "^3.6.0" 54 | }, 55 | "peerDependencies": { 56 | "vue": "^2.5.19", 57 | "vuex": "^3.0.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/classes/Payload.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject, setValue } from '../utils/object' 2 | import options from '../plugin/options' 3 | 4 | /** 5 | * Handles passing and setting of sub-property values 6 | */ 7 | export default class Payload { 8 | constructor (expr, path, value) { 9 | this.expr = expr 10 | this.path = path 11 | this.value = value 12 | } 13 | 14 | /** 15 | * Set sub-property on target 16 | * @param target 17 | */ 18 | update (target) { 19 | if (!options.deep) { 20 | console.error(`[Vuex Pathify] Unable to access sub-property for path '${this.expr}': 21 | - Set option 'deep' to 1 to allow it`) 22 | return target 23 | } 24 | 25 | const success = setValue(target, this.path, this.value, options.deep > 1) 26 | 27 | // unable to set sub-property 28 | if (!success && process.env.NODE_ENV !== 'production') { 29 | console.error(`[Vuex Pathify] Unable to create sub-property for path '${this.expr}': 30 | - Set option 'deep' to 2 to allow it`) 31 | return target 32 | } 33 | 34 | // set sub-property 35 | return Array.isArray(target) 36 | ? [].concat(target) 37 | : Object.assign({}, target) 38 | } 39 | } 40 | 41 | /** 42 | * Test if value is a serialized Payload 43 | * 44 | * @see https://github.com/davestewart/vuex-pathify/pull/125 45 | */ 46 | Payload.isSerialized = function (value) { 47 | return isPlainObject(value) 48 | && 'expr' in value 49 | && 'path' in value 50 | && 'value' in value 51 | } 52 | -------------------------------------------------------------------------------- /src/helpers/accessors.js: -------------------------------------------------------------------------------- 1 | import { clone, isObject } from '../utils/object' 2 | import { makeGetter, makeSetter } from '../services/store' 3 | 4 | export default function (store) { 5 | 6 | /** 7 | * Set a property on the store, automatically using actions or mutations 8 | * 9 | * @param {string} path The path to the store member 10 | * @param {*} value The value to set 11 | * @returns {Promise|*} Any return value from the action / commit 12 | */ 13 | store.set = function (path, value) { 14 | const setter = makeSetter(store, path) 15 | if (typeof setter !== 'undefined') { 16 | return setter(value) 17 | } 18 | } 19 | 20 | /** 21 | * Get a property from the store, automatically using getters or state 22 | * 23 | * @param {string} path The path to the store member 24 | * @param {*} args Optional getter function parameters 25 | * @returns {*|undefined} The state value / getter value / getter function / or undefined 26 | */ 27 | store.get = function (path, ...args) { 28 | const getter = makeGetter(store, path) 29 | if (typeof getter !== 'undefined') { 30 | const value = getter() 31 | return typeof value === 'function' 32 | ? value(...args) 33 | : value 34 | } 35 | } 36 | 37 | /** 38 | * Get a copy of a property from the store, automatically using actions or mutations 39 | * 40 | * @param {string} path The path to the store member 41 | * @param {*} args Optional getter function parameters 42 | * @returns {*|undefined} The value, or undefined 43 | */ 44 | store.copy = function (path, ...args) { 45 | const value = store.get(path, ...args) 46 | return isObject(value) 47 | ? clone(value) 48 | : value 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/helpers/component.js: -------------------------------------------------------------------------------- 1 | import { makeGetter, makeSetter } from '../services/store' 2 | import { expandGet, expandSync, expandCall } from '../services/wildcards' 3 | import { makePaths } from '../services/paths' 4 | import vuex from './vuex' 5 | 6 | // ------------------------------------------------------------------------------------------------------------------- 7 | // entry 8 | // ------------------------------------------------------------------------------------------------------------------- 9 | 10 | export function get (path, props) { 11 | return make(path, props, getOne, function (path) { 12 | return expandGet(path, vuex.store.state, vuex.store.getters) 13 | }) 14 | } 15 | 16 | export function sync (path, props) { 17 | return make(path, props, syncOne, function (path) { 18 | return expandSync(path, vuex.store.state) 19 | }) 20 | } 21 | 22 | export function call (path, props) { 23 | return make(path, props, callOne, function (path) { 24 | return expandCall(path, vuex.store._actions) 25 | }) 26 | } 27 | 28 | // ------------------------------------------------------------------------------------------------------------------- 29 | // utility 30 | // ------------------------------------------------------------------------------------------------------------------- 31 | 32 | /** 33 | * Creates multiple 2-way vue:vuex computed properties 34 | * 35 | * The function has multiple usages: 36 | * 37 | * 1. multiple properties from multiple modules 38 | * 39 | * - @usage ...sync({foo: 'module1/foo', bar: 'module2/bar'}) 40 | * 41 | * - @param {Object} props a hash of key:path state/getter or commit/action references 42 | * 43 | * 2. multiple properties from a single module (object shorthand) 44 | * 45 | * - @usage ...sync('module', {foo: 'foo', bar: 'bar'}) 46 | * 47 | * - @param {string} path a path to a module 48 | * - @param {Object} props a hash of key:prop state/getter or commit/action references 49 | * 50 | * 3. multiple properties from a single module (array shorthand) 51 | * 52 | * - @usage ...sync('module', ['foo', 'bar']) 53 | * 54 | * - @param {string} path a path to a module 55 | * - @param {Array} props an Array of state/getter or commit/action references 56 | * 57 | * Where different getter / setters need to be specified, pass getter and setter in 58 | * the same string, separating with a | character: 59 | * 60 | * - @usage ...sync('module', ['foo|updateFoo']) 61 | * 62 | * @param {string|Object} path a path to a module, or a hash of state/getter or commit/action references 63 | * @param {Object|Array} props a hash of state/getter or commit/action references 64 | * @param {Function} fnHandler a callback function to create the handler 65 | * @param {Function} fnResolver 66 | * @returns {{set, get}} a hash of Objects 67 | */ 68 | export function make (path, props, fnHandler, fnResolver) { 69 | // expand path / props 70 | const data = makePaths(path, props, fnResolver) 71 | 72 | // handle single paths 73 | if (typeof data === 'string') { 74 | return fnHandler(data) 75 | } 76 | 77 | // handle multiple properties 78 | Object 79 | .keys(data) 80 | .forEach(key => { 81 | data[key] = fnHandler(data[key]) 82 | }) 83 | return data 84 | } 85 | 86 | // ------------------------------------------------------------------------------------------------------------------- 87 | // one 88 | // ------------------------------------------------------------------------------------------------------------------- 89 | 90 | /** 91 | * Creates a single 2-way vue:vuex computed property 92 | * 93 | * @param {string} path a path to a state/getter reference. Path can contain an optional commit / action reference, separated by a |, i.e. foo/bar|updateBar 94 | * @returns {Object} a single get/set Object 95 | */ 96 | export function syncOne (path) { 97 | let [getter, setter] = path.split('|') 98 | if (setter) { 99 | setter = getter.replace(/\w+!?$/, setter.replace('!', '') + '!') 100 | } 101 | return getter && setter 102 | ? { get: getOne(getter, true), set: setOne(setter) } 103 | : { get: getOne(getter, true), set: setOne(getter) } 104 | } 105 | 106 | /** 107 | * Creates a single 1-way vue:vuex computed getter 108 | * 109 | * @param {string} path A path to a state/getter reference 110 | * @param {boolean} [stateOnly] An optional flag to get from state only (used when syncing) 111 | * @returns {Object} A single getter function 112 | */ 113 | export function getOne (path, stateOnly) { 114 | let getter, store 115 | return function (...args) { 116 | if (!this.$store) { 117 | throw new Error('[Vuex Pathify] Unexpected condition: this.$store is undefined.\n\nThis is a known edge case with some setups and will cause future lookups to fail') 118 | } 119 | if (!getter || store !== this.$store) { 120 | store = this.$store 121 | getter = makeGetter(store, path, stateOnly) 122 | } 123 | return getter.call(this, ...args) 124 | } 125 | } 126 | 127 | /** 128 | * Creates a single 1-way vue:vuex setter 129 | * 130 | * @param {string} path a path to an action/commit reference 131 | * @returns {Function} a single setter function 132 | */ 133 | export function setOne (path) { 134 | let setter, store 135 | return function (value) { 136 | if (!setter || store !== this.$store) { 137 | store = this.$store 138 | setter = makeSetter(store, path) 139 | } 140 | this.$nextTick(() => this.$emit('sync', path, value)) 141 | return setter.call(this, value) 142 | } 143 | } 144 | 145 | /** 146 | * Creates a single action dispatcher 147 | * 148 | * @param {string} path a path to an action/commit reference 149 | * @returns {Function} a single setter function 150 | */ 151 | export function callOne (path) { 152 | return function (value) { 153 | return this.$store.dispatch(path, value) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/helpers/decorators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | * @description Decorators for Vuex Pathify component helpers 4 | * 5 | * For example: 6 | * ```js 7 | * @Component 8 | * class MyComponent extends Vue { 9 | * @Get('name') 10 | * @Set('name') 11 | * @Call('setName') 12 | * } 13 | * ``` 14 | */ 15 | 16 | import { call, get, sync } from './component' 17 | import { createDecorator } from 'vue-class-component' 18 | 19 | /** 20 | * Decorator for `get` component helper. 21 | * @param {string} path The path to store property 22 | * @returns {VueDecorator} Vue decorator to be used in Vue class component. 23 | */ 24 | function Get (path) { 25 | if (typeof path !== 'string' || arguments.length > 1) { throw new Error('Property decorators can only be used for single property access') } 26 | return createDecorator((options, key) => { 27 | if (!options.computed) options.computed = {} 28 | options.computed[key] = get(path) 29 | }) 30 | } 31 | 32 | /** 33 | * Decorator for `sync` component helper. 34 | * @param {string} path The path to store property 35 | * @returns {VueDecorator} Vue decorator to be used in Vue class component. 36 | */ 37 | function Sync (path) { 38 | if (typeof path !== 'string' || arguments.length > 1) { throw new Error('Property decorators can only be used for single property access') } 39 | return createDecorator((options, key) => { 40 | if (!options.computed) options.computed = {} 41 | options.computed[key] = sync(path) 42 | }) 43 | } 44 | 45 | /** 46 | * Decorator for `call` component helper. 47 | * @param {string} path The path to store property 48 | * @returns {VueDecorator} Vue decorator to be used in Vue class component. 49 | */ 50 | function Call (path) { 51 | if (typeof path !== 'string' || arguments.length > 1) { throw new Error('Property decorators can only be used for single property access') } 52 | return createDecorator((options, key) => { 53 | if (!options.methods) options.methods = {} 54 | options.methods[key] = call(path) 55 | }) 56 | } 57 | 58 | export { Get, Sync, Call } 59 | -------------------------------------------------------------------------------- /src/helpers/modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function to generate a mixin that registers module and computed properties on component creation 3 | * 4 | * @param {string|Array} path The path to register the Vuex module on 5 | * @param {object} module The module definition to register when the 6 | * @param {function} callback A callback returning store members to be added to the component definition 7 | * @param {object} [options] Optional Vuex module registration options 8 | * @returns {object} The mixin 9 | */ 10 | export function registerModule (path, module, callback, options) { 11 | return { 12 | beforeCreate () { 13 | this.$store.registerModule(path, module, options) 14 | const members = callback() 15 | this.$options.computed = Object.assign(this.$options.computed || {}, members.computed || {}) 16 | this.$options.methods = Object.assign(this.$options.methods || {}, members.methods || {}) 17 | }, 18 | 19 | destroyed () { 20 | this.$store.unregisterModule(path) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/helpers/store.js: -------------------------------------------------------------------------------- 1 | import { getKeys } from '../utils/object' 2 | import { resolveName } from '../services/resolver' 3 | import Payload from '../classes/Payload' 4 | 5 | /** 6 | * Utility function to grab keys for state 7 | * 8 | * @param {Object|Function|Array|String} state State object, state function, array or string of key names 9 | * @returns {Array} 10 | */ 11 | function getStateKeys (state) { 12 | return getKeys(typeof state === 'function' ? state() : state) 13 | } 14 | 15 | /** 16 | * Helper function to mass-create default getter functions for an existing state object 17 | * 18 | * Note that you don't need to create top-level getter functions if using $store.get(...) 19 | * 20 | * @param {Object|Function|Array|String} state State object, state function, array or string of key names 21 | */ 22 | export function makeGetters (state) { 23 | return getStateKeys(state) 24 | .reduce(function (obj, key) { 25 | const getter = resolveName('getters', key) 26 | obj[getter] = function (state) { 27 | return state[key] 28 | } 29 | return obj 30 | }, {}) 31 | } 32 | 33 | /** 34 | * Helper function to mass-create default mutation functions for an existing state object 35 | * 36 | * @param {Object|Function|Array|String} state State object, state function, array or string of key names 37 | */ 38 | export function makeMutations (state) { 39 | return getStateKeys(state) 40 | .reduce(function (obj, key) { 41 | const mutation = resolveName('mutations', key) 42 | obj[mutation] = function (state, value) { 43 | if (value instanceof Payload) { 44 | value = value.update(state[key]) 45 | } 46 | else if (Payload.isSerialized(value)) { 47 | value = Payload.prototype.update.call(value, state[key]) 48 | } 49 | state[key] = value 50 | } 51 | return obj 52 | }, {}) 53 | } 54 | 55 | /** 56 | * Helper function to mass-create default actions functions for an existing state object 57 | * 58 | * @param {Object|Function|Array|String} state State object, state function, array or string of key names 59 | */ 60 | export function makeActions (state) { 61 | return getStateKeys(state) 62 | .reduce(function (obj, key) { 63 | const action = resolveName('actions', key) 64 | const mutation = resolveName('mutations', key) 65 | obj[action] = function ({ commit }, value) { 66 | commit(mutation, value) 67 | } 68 | return obj 69 | }, {}) 70 | } 71 | 72 | export default { 73 | getters: makeGetters, 74 | mutations: makeMutations, 75 | actions: makeActions, 76 | } 77 | -------------------------------------------------------------------------------- /src/helpers/vuex.js: -------------------------------------------------------------------------------- 1 | const vuex = { 2 | /** 3 | * THIS OBJECT IS REPLACED AT RUNTIME WITH THE ACTUAL VUEX STORE 4 | */ 5 | store: { 6 | state: null, 7 | 8 | commit () { 9 | if (process.env.NODE_ENV !== 'production') { 10 | console.error('[Vuex Pathify] Plugin not initialized!') 11 | } 12 | }, 13 | 14 | dispatch () { 15 | if (process.env.NODE_ENV !== 'production') { 16 | console.error('[Vuex Pathify] Plugin not initialized!') 17 | } 18 | } 19 | } 20 | } 21 | 22 | export function commit (...args) { 23 | vuex.store.commit(...args) 24 | } 25 | 26 | export function dispatch (...args) { 27 | return vuex.store.dispatch(...args) 28 | } 29 | 30 | export default vuex 31 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import pathify from './plugin/pathify' 2 | 3 | import make from './helpers/store' 4 | import { get, sync, call } from './helpers/component' 5 | import { Get, Sync, Call } from './helpers/decorators' 6 | import { commit, dispatch } from './helpers/vuex' 7 | import { registerModule } from './helpers/modules' 8 | import Payload from './classes/Payload' 9 | 10 | export default pathify 11 | 12 | export { 13 | // store 14 | make, 15 | Payload, 16 | 17 | // component 18 | get, 19 | sync, 20 | call, 21 | 22 | // decorators 23 | Get, 24 | Sync, 25 | Call, 26 | 27 | // vuex 28 | commit, 29 | dispatch, 30 | registerModule, 31 | } 32 | -------------------------------------------------------------------------------- /src/plugin/debug.js: -------------------------------------------------------------------------------- 1 | import options from './options' 2 | import { resolveName } from '../services/resolver' 3 | 4 | function resolve (type) { 5 | return resolveName(type, 'value') 6 | } 7 | 8 | export default function debug () { 9 | console.log(` 10 | [Vuex Pathify] Options: 11 | 12 | Mapping (${typeof options.mapping === 'function' ? 'custom' : options.mapping}) 13 | ------------------------------- 14 | path : value 15 | state : ${resolve('state')} 16 | getters : ${resolve('getters')} 17 | actions : ${resolve('actions')} 18 | mutations : ${resolve('mutations')} 19 | 20 | Settings 21 | ------------------------------- 22 | strict : ${options.strict} 23 | cache : ${options.cache} 24 | deep : ${options.deep} 25 | 26 | `) 27 | } 28 | -------------------------------------------------------------------------------- /src/plugin/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mapping: 'standard', // map states to store members using the "standard" scheme 3 | strict: true, // throw an error if the store member cannot be found 4 | cache: true, // cache generated functions for faster re-use 5 | deep: 1, // allow sub-property access, but not creation 6 | } 7 | -------------------------------------------------------------------------------- /src/plugin/pathify.js: -------------------------------------------------------------------------------- 1 | // plugin 2 | import vuex from '../helpers/vuex' 3 | 4 | // options 5 | import accessorize from '../helpers/accessors' 6 | import options from './options' 7 | import debug from './debug' 8 | 9 | /** 10 | * Store plugin which updates the store object with set() and get() methods 11 | * 12 | * @param {Object} store The store object 13 | */ 14 | function plugin (store) { 15 | 16 | // cache store instance 17 | vuex.store = store 18 | 19 | // add pathify accessors 20 | accessorize(store) 21 | } 22 | 23 | export default { 24 | options, 25 | plugin, 26 | debug, 27 | } 28 | -------------------------------------------------------------------------------- /src/services/formatters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | camel: function (...args) { 3 | return args.shift() + args 4 | .map(text => text.replace(/\w/, c => c.toUpperCase())) 5 | .join('') 6 | }, 7 | 8 | snake: function (...args) { 9 | return this 10 | .camel(...args) 11 | .replace(/([a-z])([A-Z])/g, (match, a, b) => a + '_' + b) 12 | .toLowerCase() 13 | }, 14 | 15 | const: function (...args) { 16 | return this 17 | .snake(...args) 18 | .toUpperCase() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/services/paths.js: -------------------------------------------------------------------------------- 1 | import { isObject } from '../utils/object' 2 | 3 | /** 4 | * Helper function to convert Pathify path syntax paths to objects 5 | * 6 | * Handles: 7 | * 8 | * - string path 9 | * - object and array formats 10 | * - path + object/array format 11 | * - wildcards in path 12 | * 13 | * Returns a single string, or hash of key => paths 14 | * 15 | * @param {string|object|array} [path] An optional path prefix 16 | * @param {object} props An optional hash or array of paths / segments 17 | * @param {function} fnResolver A function to resolve wildcards 18 | * @returns {object|string} 19 | */ 20 | export function makePaths (path, props, fnResolver) { 21 | // handle wildcards 22 | if (typeof path === 'string' && path.indexOf('*') > -1) { 23 | return makePathsHash(fnResolver(path)) 24 | } 25 | 26 | // handle array as path 27 | if (Array.isArray(path)) { 28 | return makePathsHash(path) 29 | } 30 | 31 | // handle object as path 32 | if (isObject(path)) { 33 | props = path 34 | path = '' 35 | } 36 | 37 | // if props is an array 38 | if (Array.isArray(props)) { 39 | const paths = props 40 | .map(prop => { 41 | return makePath(path, prop) 42 | }) 43 | return makePathsHash(paths) 44 | } 45 | 46 | // if props is an object 47 | if (isObject(props)) { 48 | return Object 49 | .keys(props) 50 | .reduce((paths, key) => { 51 | paths[key] = makePath(path, props[key]) 52 | return paths 53 | }, {}) 54 | } 55 | 56 | // if path is a single string without wildcards 57 | return path 58 | } 59 | 60 | /** 61 | * Helper function to concatenate two path components into a valid path 62 | * 63 | * Handles one or no "/" "@" or '.' characters in either string 64 | * 65 | * @param {string} path 66 | * @param {string} target 67 | * @returns {string} 68 | */ 69 | export function makePath (path, target = '') { 70 | path = path.replace(/\/+$/, '') 71 | const value = path.indexOf('@') > -1 72 | ? path + '.' + target 73 | : path + '/' + target 74 | return value 75 | .replace(/^\/|[.@/]+$/, '') 76 | .replace(/\/@/, '@') 77 | .replace(/@\./, '@') 78 | } 79 | 80 | /** 81 | * Helper function to convert an array of paths to a hash 82 | * 83 | * Uses the last path segment as the key 84 | * 85 | * @param {string[]} paths An array of paths to convert to a hash 86 | * @returns {object} A hash of paths 87 | */ 88 | export function makePathsHash (paths) { 89 | return paths.reduce((paths, path) => { 90 | const key = path.match(/\w+$/) 91 | paths[key] = path 92 | return paths 93 | }, {}) 94 | } 95 | -------------------------------------------------------------------------------- /src/services/resolver.js: -------------------------------------------------------------------------------- 1 | import { hasValue } from '../utils/object' 2 | import options from '../plugin/options' 3 | import formatters from './formatters' 4 | 5 | /** 6 | * Map of store members 7 | */ 8 | const members = { 9 | state: 'state', 10 | getters: 'getters', 11 | actions: '_actions', 12 | mutations: '_mutations', 13 | } 14 | 15 | /** 16 | * Map of default resolver functions 17 | */ 18 | const resolvers = { 19 | 20 | /** 21 | * Standard name mapping function 22 | * 23 | * Adheres to seemingly the most common Vuex naming pattern 24 | * 25 | * @param {string} type The member type, i.e state, getters, mutations, or actions 26 | * @param {string} name The name of the property being targeted, i.e. value 27 | * @param {object} formatters A formatters object with common format functions, camel, snake, const 28 | * @returns {string} 29 | */ 30 | standard (type, name, formatters) { 31 | switch (type) { 32 | case 'mutations': 33 | return formatters.const('set', name) // SET_BAR 34 | case 'actions': 35 | return formatters.camel('set', name) // setBar 36 | } 37 | return name // bar 38 | }, 39 | 40 | /** 41 | * Simple name mapping function 42 | */ 43 | simple (type, name, formatters) { 44 | if (type === 'actions') { 45 | return formatters.camel('set', name) // setBar 46 | } 47 | return name // bar 48 | }, 49 | 50 | } 51 | 52 | /** 53 | * Configured resolver 54 | */ 55 | let resolver 56 | 57 | /** 58 | * Internal function to resolve member name using configured mapping function 59 | * 60 | * @param {string} type The member type, i.e. actions 61 | * @param {string} name The supplied path member id, i.e. value 62 | * @returns {string} The resolved member name, i.e. SET_VALUE 63 | */ 64 | export function resolveName (type, name) { 65 | // bypass resolver 66 | if (name.match(/!$/)) { 67 | return name.substr(0, name.length - 1) 68 | } 69 | 70 | // configured resolver 71 | let fn = resolver 72 | 73 | // unconfigured resolver! (runs once) 74 | if (!fn) { 75 | if (typeof options.mapping === 'function') { 76 | fn = options.mapping 77 | } 78 | else { 79 | fn = resolvers[options.mapping] 80 | if (!fn) { 81 | throw new Error(`[Vuex Pathify] Unknown mapping '${options.mapping}' in options 82 | - Choose one of '${Object.keys(resolvers).join("', '")}' 83 | - Or, supply a custom function 84 | `) 85 | } 86 | } 87 | 88 | resolver = fn 89 | } 90 | 91 | // resolve! 92 | return resolver(type, name, formatters) 93 | } 94 | 95 | /** 96 | * Creates a resolver object that caches properties and can resolve store member properties 97 | * 98 | * @param {object} store The Vuex store instance 99 | * @param {string} path A pathify path to the store target, i.e. 'foo/bar@a.b.c' 100 | * @returns {object} 101 | */ 102 | export function resolve (store, path) { 103 | // state 104 | const absPath = path.replace(/[/@!]+/g, '.') 105 | 106 | // paths 107 | const [statePath, objPath] = path.split('@') 108 | 109 | // parent 110 | let modPath, trgName 111 | if (statePath.indexOf('/') > -1) { 112 | const keys = statePath.split('/') 113 | trgName = keys.pop() 114 | modPath = keys.join('/') 115 | } 116 | else { 117 | trgName = statePath 118 | } 119 | 120 | // throw error if module does not exist 121 | if (modPath && !store._modulesNamespaceMap[modPath + '/']) { 122 | throw new Error(`[Vuex Pathify] Unknown module '${modPath}' via path '${path}'`) 123 | } 124 | 125 | // resolve targets 126 | return { 127 | absPath: absPath, 128 | module: modPath, 129 | target: statePath, 130 | name: trgName.replace('!', ''), 131 | isDynamic: path.indexOf(':') > -1, 132 | 133 | /** 134 | * Returns properties about the targeted member 135 | * 136 | * @param {string} type The member type, i.e state, getters, mutations, or actions 137 | * @returns {{exists: boolean, member: object, type: string, path: string}} 138 | */ 139 | get: function (type) { 140 | // targeted member, i.e. store._getters 141 | const member = store[members[type]] 142 | 143 | // resolved target name, i.e. SET_VALUE 144 | const resName = resolveName(type, trgName) 145 | 146 | // target path, i.e. store._getters['module/SET_VALUE'] 147 | const trgPath = modPath 148 | ? modPath + '/' + resName 149 | : resName 150 | 151 | // return values 152 | return { 153 | exists: type === 'state' 154 | ? hasValue(member, trgPath) 155 | : trgPath in member, 156 | member, 157 | trgPath, 158 | trgName: resName, 159 | objPath, 160 | } 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * Error generation function for accessors 167 | */ 168 | export function getError (path, resolver, aName, a, bName, b) { 169 | let error = `[Vuex Pathify] Unable to map path '${path}':` 170 | if (path.indexOf('!') > -1) { 171 | error += ` 172 | - Did not find ${aName} or ${bName} named '${resolver.name}' on ${resolver.module ? `module '${resolver.module}'` : 'root store'}` 173 | } 174 | else { 175 | const aText = a 176 | ? `${aName} '${a.trgName}' or ` 177 | : '' 178 | const bText = `${bName} '${b.trgName}'` 179 | error += ` 180 | - Did not find ${aText}${bText} on ${resolver.module ? `module '${resolver.module}'` : 'store'} 181 | - Use direct syntax '${resolver.target.replace(/(@|$)/, '!$1')}' (if member exists) to target directly` 182 | } 183 | return error 184 | } 185 | 186 | -------------------------------------------------------------------------------- /src/services/store.js: -------------------------------------------------------------------------------- 1 | import { getValue } from '../utils/object' 2 | import { getError, resolve } from './resolver' 3 | import Payload from '../classes/Payload' 4 | import options from '../plugin/options' 5 | 6 | /** 7 | * Creates a setter function for the store, automatically targeting actions or mutations 8 | * 9 | * Also supports setting of sub-properties as part of the path 10 | * 11 | * @see documentation for more detail 12 | * 13 | * @param {Object} store The store object 14 | * @param {string} path The path to the target node 15 | * @returns {*|Promise} The return value from the commit() or dispatch() 16 | */ 17 | export function makeSetter (store, path) { 18 | const resolver = resolve(store, path) 19 | 20 | const action = resolver.get('actions') 21 | if (action.exists) { 22 | return function (value) { 23 | const payload = action.objPath 24 | ? new Payload(path, action.objPath, value) 25 | : value 26 | return store.dispatch(action.trgPath, payload) 27 | } 28 | } 29 | 30 | let mutation = resolver.get('mutations') 31 | if (mutation.exists || resolver.isDynamic) { 32 | return function (value) { 33 | // rebuild mutation if using dynamic path 34 | if (resolver.isDynamic) { 35 | const interpolated = interpolate(path, this) 36 | mutation = resolve(store, interpolated).get('mutations') 37 | } 38 | const payload = mutation.objPath 39 | ? new Payload(path, mutation.objPath, value) 40 | : value 41 | return store.commit(mutation.trgPath, payload) 42 | } 43 | } 44 | 45 | if (process.env.NODE_ENV !== 'production') { 46 | console.error(getError(path, resolver, 'action', action, 'mutation', mutation)) 47 | } 48 | 49 | return value => {} 50 | } 51 | 52 | /** 53 | * Creates a getter function for the store, automatically targeting getters or state 54 | * 55 | * Also supports returning of sub-properties as part of the path 56 | * 57 | * @see documentation for more detail 58 | * 59 | * @param {Object} store The store object 60 | * @param {string} path The path to the target node 61 | * @param {boolean} [stateOnly] An optional flag to get from state only (used when syncing) 62 | * @returns {*|Function} The state value or getter function 63 | */ 64 | export function makeGetter (store, path, stateOnly) { 65 | const resolver = resolve(store, path) 66 | 67 | // for sync, we don't want to read only from state 68 | let getter 69 | if (!stateOnly) { 70 | getter = resolver.get('getters') 71 | if (getter.exists) { 72 | return function () { 73 | const value = getter.member[getter.trgPath] 74 | return getter.objPath 75 | ? getValueIfEnabled(path, value, getter.objPath) 76 | : value 77 | } 78 | } 79 | } 80 | 81 | const state = resolver.get('state') 82 | if (state.exists || resolver.isDynamic) { 83 | return function () { 84 | const absPath = resolver.isDynamic 85 | ? interpolate(resolver.absPath, this) 86 | : resolver.absPath 87 | return getValueIfEnabled(path, store.state, absPath) 88 | } 89 | } 90 | 91 | if (process.env.NODE_ENV !== 'production') { 92 | console.error(getError(path, resolver, 'getter', getter, 'state', state)) 93 | } 94 | 95 | return () => {} 96 | } 97 | 98 | /** 99 | * Utility function to get value from store, but only if options allow 100 | * 101 | * @param {string} expr The full path expression 102 | * @param {object} source The source object to get property from 103 | * @param {string} path The full dot-path on the source object 104 | * @returns {*} 105 | */ 106 | function getValueIfEnabled (expr, source, path) { 107 | if (!options.deep && expr.indexOf('@') > -1) { 108 | console.error(`[Vuex Pathify] Unable to access sub-property for path '${expr}': 109 | - Set option 'deep' to 1 to allow it`) 110 | return 111 | } 112 | return getValue(source, path) 113 | } 114 | 115 | /** 116 | * Utility function to interpolate a string with properties 117 | * @param {string} path The path containing interpolation :tokens 118 | * @param {object} scope The scope containing properties to be used 119 | * @return {string} 120 | */ 121 | function interpolate (path, scope) { 122 | return path.replace(/:(\w+)/g, function replace (all, token) { 123 | if (!(token in scope)) { 124 | console.error(`Error resolving dynamic store path: The property "${token}" does not exist on the scope`, scope) 125 | } 126 | return scope[token] 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /src/services/wildcards.js: -------------------------------------------------------------------------------- 1 | import { getValue } from '../utils/object' 2 | 3 | // ------------------------------------------------------------------------------------------------------------------- 4 | // external 5 | // ------------------------------------------------------------------------------------------------------------------- 6 | 7 | /** 8 | * Utility function to expand wildcard path for get() 9 | * 10 | * @param {string} path wildcard path 11 | * @param {object} state state hash 12 | * @param {object} getters getters hash 13 | * @returns {array|string} 14 | */ 15 | export function expandGet (path, state, getters) { 16 | if (!init(path, state)) { 17 | return '' 18 | } 19 | return [ 20 | ...resolveStates(path, state), 21 | ...resolveHandlers(path, getters), 22 | ] 23 | } 24 | 25 | /** 26 | * Utility function to expand wildcard path for sync() 27 | * 28 | * @param {string} path wildcard path 29 | * @param {object} state state hash 30 | * @returns {array|string} 31 | */ 32 | export function expandSync (path, state) { 33 | if (!init(path, state)) { 34 | return '' 35 | } 36 | return resolveStates(path, state) 37 | } 38 | 39 | /** 40 | * Utility function to expand wildcard path for actions() 41 | * 42 | * @param {string} path wildcard path 43 | * @param {object} actions actions hash 44 | * @returns {array|string} 45 | */ 46 | export function expandCall (path, actions) { 47 | if (!init(path, actions)) { 48 | return '' 49 | } 50 | return resolveHandlers(path, actions) 51 | } 52 | 53 | // ------------------------------------------------------------------------------------------------------------------- 54 | // internal 55 | // ------------------------------------------------------------------------------------------------------------------- 56 | 57 | /** 58 | * Helper function to resolve state properties from a wildcard path 59 | * 60 | * Note: this function traverses into the state object and any properties / sub-properties 61 | * 62 | * @param {string} path A path with a wildcard at the end 63 | * @param {object} state A state object on which to look up the sub-properties 64 | * @returns {string[]} An array of paths 65 | */ 66 | export function resolveStates (path, state) { 67 | // grab segments 68 | const last = path.match(/([^/@\.]+)$/)[1] 69 | const main = path.substring(0, path.length - last.length) 70 | const keys = main.replace(/\W+$/, '').split(/[/@.]/) 71 | 72 | // find state parent 73 | let obj = main 74 | ? getValue(state, keys) 75 | : state 76 | if (!obj) { 77 | console.error(`[Vuex Pathify] Unable to expand wildcard path '${path}': 78 | - It looks like '${main.replace(/\W+$/, '')}' does not resolve to an existing state property`) 79 | return [] 80 | } 81 | 82 | // filter children 83 | const rx = new RegExp('^' + last.replace(/\*/g, '\\w+') + '$') 84 | return Object 85 | .keys(obj) 86 | .filter(key => rx.test(key)) 87 | .map(key => main + key) 88 | } 89 | 90 | /** 91 | * Helper function to resolve getters, actions or mutations from a wildcard path 92 | * 93 | * Note: this function filters the top-level flat hash of members 94 | * 95 | * @param {string} path A path with a wildcard at the end 96 | * @param {object} hash A hash on which to filter by key => wildcard 97 | * @returns {string[]} An array of paths 98 | */ 99 | export function resolveHandlers (path, hash) { 100 | const rx = new RegExp('^' + path.replace(/\*/g, '\\w+') + '$') 101 | return Object.keys(hash).filter(key => rx.test(key)) 102 | } 103 | 104 | // ------------------------------------------------------------------------------------------------------------------- 105 | // utility 106 | // ------------------------------------------------------------------------------------------------------------------- 107 | 108 | /** 109 | * Pre-flight check for wildcard paths 110 | * 111 | * @param {string} path 112 | * @param {object} state 113 | * @returns {boolean} 114 | */ 115 | export function init (path, state) { 116 | // only wildcards in final path segment are supported 117 | if (path.indexOf('*') > -1 && /\*.*[/@.]/.test(path)) { 118 | console.error(`[Vuex Pathify] Invalid wildcard placement for path '${path}': 119 | - Wildcards may only be used in the last segment of a path`) 120 | return false 121 | } 122 | 123 | // edge case where store sometimes doesn't exist 124 | if (!state) { 125 | console.error(`[Vuex Pathify] Unable to expand wildcard path '${path}': 126 | - The usual reason for this is that the router was set up before the store 127 | - Make sure the store is imported before the router, then reload`) 128 | return false 129 | } 130 | 131 | return true 132 | } 133 | -------------------------------------------------------------------------------- /src/utils/object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests whether a passed value is an Object 3 | * 4 | * @param {*} value The value to be assessed 5 | * @returns {boolean} Whether the value is a true Object 6 | */ 7 | export function isPlainObject (value) { 8 | return isObject(value) && !Array.isArray(value) 9 | } 10 | 11 | /** 12 | * Tests whether a passed value is an Object or Array 13 | * 14 | * @param {*} value The value to be assessed 15 | * @returns {boolean} Whether the value is an Object or Array 16 | */ 17 | export function isObject (value) { 18 | return !!value && typeof value === 'object' 19 | } 20 | 21 | /** 22 | * Tests whether a string is numeric 23 | * 24 | * @param {string|number} value The value to be assessed 25 | * @returns {boolean} 26 | */ 27 | export function isNumeric (value) { 28 | return typeof value === 'number' || /^\d+$/.test(value) 29 | } 30 | 31 | /** 32 | * Tests whether a passed value is an Object and has the specified key 33 | * 34 | * @param {Object} obj The source object 35 | * @param {string} key The key to check that exists 36 | * @returns {boolean} Whether the predicate is satisfied 37 | */ 38 | export function hasKey (obj, key) { 39 | return isObject(obj) && key in obj 40 | } 41 | 42 | /** 43 | * Gets an array of keys from a value 44 | * 45 | * The function handles various types: 46 | * 47 | * - string - match all words 48 | * - object - return keys 49 | * - array - return a string array of its values 50 | * 51 | * @param {*} value The value to get keys from 52 | * @returns {Array} 53 | */ 54 | export function getKeys (value) { 55 | return !value 56 | ? [] 57 | : Array.isArray(value) 58 | ? value.map(key => String(key)) 59 | : typeof value === 'object' 60 | ? Object.keys(value) 61 | : typeof value === 'string' 62 | ? value.match(/[-$\w]+/g) || [] 63 | : [] 64 | } 65 | 66 | /** 67 | * Gets a value from an object, based on a path to the property 68 | * 69 | * @param {Object} obj The Object to get the value from 70 | * @param {string|Array|Object} [path] The optional path to a sub-property 71 | * @returns {*} 72 | */ 73 | export function getValue (obj, path) { 74 | let value = obj 75 | const keys = getKeys(path) 76 | 77 | keys.every(function (key) { 78 | const valid = isObject(value) && value.hasOwnProperty(key) 79 | value = valid ? value[key] : void 0 80 | return valid 81 | }) 82 | return value 83 | } 84 | 85 | /** 86 | * Sets a value on an object, based on a path to the property 87 | * 88 | * @param {Object} obj The Object to set the value on 89 | * @param {string|Array|Object} path The path to a sub-property 90 | * @param {*} value The value to set 91 | * @param {boolean} [create] Optional flag to create sub-properties; defaults to false 92 | * @returns {Boolean} True or false, depending if value was set 93 | */ 94 | export function setValue (obj, path, value, create = false) { 95 | const keys = getKeys(path) 96 | return keys.reduce((obj, key, index) => { 97 | // early return if no object 98 | if (!obj) { 99 | return false 100 | } 101 | 102 | // convert key to index if obj is an array and key is numeric 103 | if (Array.isArray(obj) && isNumeric(key)) { 104 | key = parseInt(key) 105 | } 106 | 107 | // if we're at the end of the path, set the value 108 | if (index === keys.length - 1) { 109 | obj[key] = value 110 | return true 111 | } 112 | 113 | // if the target property doesn't exist... 114 | else if (!isObject(obj[key]) || !(key in obj)) { 115 | // ...create one, or cancel 116 | if (create) { 117 | // create object or array, depending on next key 118 | obj[key] = isNumeric(keys[index + 1]) 119 | ? [] 120 | : {} 121 | } 122 | else { 123 | return false 124 | } 125 | } 126 | 127 | // if we get here, return the target property 128 | return obj[key] 129 | }, obj) 130 | } 131 | 132 | /** 133 | * Checks an object has a property, based on a path to the property 134 | * 135 | * @param {Object} obj The Object to check the value on 136 | * @param {string|Array|Object} path The path to a sub-property 137 | * @returns {boolean} Boolean true or false 138 | */ 139 | export function hasValue (obj, path) { 140 | let keys = getKeys(path) 141 | if (isObject(obj)) { 142 | while (keys.length) { 143 | let key = keys.shift() 144 | if (hasKey(obj, key)) { 145 | obj = obj[key] 146 | } 147 | else { 148 | return false 149 | } 150 | } 151 | return true 152 | } 153 | return false 154 | } 155 | 156 | export function clone (obj) { 157 | return JSON.parse(JSON.stringify(obj)) 158 | } 159 | -------------------------------------------------------------------------------- /tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex' 3 | import { make } from '../../src/main' 4 | import pathify from './pathify' 5 | 6 | Vue.use(Vuex) 7 | 8 | export function makeStore (store) { 9 | if (!store.mutations) { 10 | store.mutations = make.mutations(store.state) 11 | } 12 | return new Vuex.Store({ 13 | plugins: [pathify.plugin], 14 | ...store 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /tests/helpers/pathify.js: -------------------------------------------------------------------------------- 1 | import pathify from '../../src/main' 2 | 3 | // options 4 | pathify.options.mapping = 'simple' 5 | pathify.options.deep = 2 6 | 7 | // export 8 | export default pathify 9 | -------------------------------------------------------------------------------- /tests/store-accessors.test.js: -------------------------------------------------------------------------------- 1 | import { make } from '../src/main'; 2 | import { makeStore } from './helpers' 3 | 4 | describe('top-level state', () => { 5 | it('should get state', () => { 6 | const state = { name: 'Jack', age: 28 } 7 | const store = makeStore({ 8 | state, 9 | }) 10 | 11 | expect(store.get('name')).toEqual('Jack') 12 | expect(store.get('age')).toEqual(28) 13 | }) 14 | 15 | 16 | it('should set state', () => { 17 | const state = { name: 'Jack', age: 28 } 18 | const store = makeStore({ state }) 19 | 20 | store.set('name', 'Jill') 21 | 22 | expect(store.state.name).toEqual('Jill') 23 | }) 24 | }) 25 | 26 | describe('nested state', function () { 27 | it('should get state', () => { 28 | const state = { 29 | person: { 30 | name: 'Jack', 31 | age: 28, 32 | pets: [{ 33 | animal: 'cat', 34 | name: 'Tabby', 35 | }], 36 | }, 37 | } 38 | const store = makeStore({ 39 | state, 40 | }) 41 | 42 | expect(store.get('person@name')).toEqual('Jack') 43 | expect(store.get('person@age')).toEqual(28) 44 | expect(store.get('person@pets@[0].animal')).toEqual('cat') 45 | expect(store.get('person@pets@[0].name')).toEqual('Tabby') 46 | }) 47 | 48 | it('should set state', () => { 49 | const state = { 50 | person: { 51 | name: 'Jack', 52 | age: 28, 53 | pets: [{ 54 | animal: 'cat', 55 | name: 'Tabby', 56 | }], 57 | }, 58 | } 59 | const store = makeStore({ state }) 60 | 61 | store.set('person@name', 'Jill') 62 | store.set('person@pets[0].name', 'Spot') 63 | 64 | expect(store.state.person.name).toEqual('Jill') 65 | expect(store.state.person.pets[0].name).toEqual('Spot') 66 | }) 67 | }) 68 | 69 | describe('module state', function () { 70 | it('should get state', () => { 71 | const state = { name: 'Jack', age: 28 } 72 | const store = makeStore({ 73 | modules: { 74 | people: { namespaced: true, state } 75 | } 76 | }) 77 | 78 | expect(store.get('people/name')).toEqual('Jack') 79 | expect(store.get('people/age')).toEqual(28) 80 | }) 81 | 82 | it('should set state', () => { 83 | const state = { name: 'Jack', age: 28 } 84 | const mutations = make.mutations(state) 85 | const store = makeStore({ 86 | modules: { 87 | people: { namespaced: true, state, mutations } 88 | } 89 | }) 90 | 91 | store.set('people/name', 'Jill') 92 | 93 | expect(store.state.people.name).toEqual('Jill') 94 | }) 95 | }) 96 | 97 | describe('special functionality', function () { 98 | describe('key types', () => { 99 | const state = { object: {}, array: [] } 100 | const store = makeStore({ state }) 101 | 102 | it('alpha keys - should set a key on an object', function () { 103 | store.set('object@a1', 1) 104 | expect(store.state.object['a1']).toEqual(1) 105 | }) 106 | 107 | it('numeric keys - should set a key on an object', function () { 108 | store.set('object@1a', 1) 109 | expect(store.state.object['1a']).toEqual(1) 110 | }) 111 | 112 | it('numeric keys - should set an index on an array', function () { 113 | store.set('array@0', 1) 114 | expect(store.state.array[0]).toEqual(1) 115 | }) 116 | }) 117 | 118 | describe('object creation', () => { 119 | const state = { target: {} } 120 | const mutations = make.mutations(state) 121 | const store = makeStore({ state, mutations }) 122 | 123 | it('should create empty objects', function () { 124 | store.set('target@value', 100) 125 | expect(store.state.target.value).toEqual(100) 126 | }) 127 | 128 | it('should create empty arrays', function () { 129 | store.set('target@matrix.0.0', 100) 130 | expect(store.state.target.matrix[0][0]).toEqual(100) 131 | }) 132 | }) 133 | }) 134 | 135 | describe('serialized Payload', () => { 136 | it('serialized Payload should be interpreted', function () { 137 | const state = { name: { firstName: 'John', lastName: 'Doe' }, age: 28 } 138 | const mutations = make.mutations(state) 139 | const store = makeStore({ 140 | modules: { 141 | people: { namespaced: true, state, mutations } 142 | } 143 | }) 144 | 145 | store.commit('people/name', { expr: 'people/name@firstname', value: 'Jane', path: 'firstname' }) 146 | 147 | expect(store.get('people/name@firstname')).toEqual('Jane') 148 | expect(store.get('people/age')).toEqual(28) 149 | }) 150 | }) 151 | -------------------------------------------------------------------------------- /tests/store-helpers.test.js: -------------------------------------------------------------------------------- 1 | import { make } from '../src/main' 2 | import { makeStore } from './helpers' 3 | 4 | it('should make mutations', () => { 5 | const state = { name: 'Jack', age: 28 } 6 | const mutations = make.mutations(state) 7 | const store = makeStore({ 8 | state, 9 | mutations, 10 | }) 11 | 12 | store.commit('age', 30) 13 | store.commit('name', 'Jill') 14 | expect(store.state.name).toEqual('Jill') 15 | expect(store.state.age).toEqual(30) 16 | }) 17 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | This is the TypeScript declaration of vuex-pathify. 2 | 3 | # Testing 4 | 5 | \$ npm run test:types 6 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // See here for vuex typescript conversation: https://github.com/vuejs/vuex/issues/564 2 | 3 | import Vue from "vue"; 4 | import vuex, { 5 | Store, 6 | CommitOptions, 7 | DispatchOptions, 8 | GetterTree, 9 | ActionTree, 10 | MutationTree 11 | } from "vuex"; 12 | import { createDecorator } from "vue-class-component"; 13 | 14 | /************************************************************************ 15 | *** DEFAULT EXPORT *** 16 | ***********************************************************************/ 17 | type MappingTypes = "state" | "getters" | "mutations" | "actions"; 18 | type MappingPresets = "standard" | "simple"; 19 | type MappingFunction = ( 20 | type: MappingTypes, 21 | name: string, 22 | formatters: MappingFormatters 23 | ) => string; 24 | 25 | interface MappingFormatters { 26 | camel: (...args: string[]) => string; 27 | snake: (...args: string[]) => string; 28 | const: (...args: string[]) => string; 29 | } 30 | 31 | interface Options { 32 | mapping: MappingPresets | MappingFunction; 33 | deep: 1 | 2 | 3; 34 | strict: boolean; 35 | cache: boolean; 36 | } 37 | 38 | interface DefaultExport { 39 | debug: () => void; 40 | plugin: (store: Store) => void; 41 | options: Options; 42 | } 43 | 44 | declare const defaultExport: DefaultExport; 45 | 46 | export default defaultExport; 47 | 48 | /************************************************************************ 49 | *** NAMED EXPORTS *** 50 | ***********************************************************************/ 51 | 52 | /*-------------------------------------------------------------------------- 53 | SHARED 54 | ------------------------------------------------------------------------*/ 55 | type GetAccessor = () => T; 56 | type SetAccessor = (newValue: T) => T; // TODO: Do setters always return same type as input. 57 | 58 | /*-------------------------------------------------------------------------- 59 | make 60 | ------------------------------------------------------------------------*/ 61 | type StateFunction = () => T; 62 | 63 | interface Make { 64 | mutations: ( 65 | state: State | StateFunction 66 | ) => MutationTree; 67 | 68 | actions: ( 69 | state: State | StateFunction 70 | ) => ActionTree; 71 | 72 | getters: ( 73 | state: State | StateFunction 74 | ) => GetterTree; 75 | } 76 | 77 | export const make: Make; 78 | 79 | /*-------------------------------------------------------------------------- 80 | Payload 81 | ------------------------------------------------------------------------*/ 82 | // TODO: Not documented/public class, may need refinement by module author. 83 | export class Payload { 84 | constructor(expr: string, path: string, value: any); 85 | expr: string; 86 | path: string; 87 | value: any; 88 | update(target: T): T; // TODO: Needs details. Define an interface instead of object. And be sure that return type is same as input. 89 | } 90 | 91 | /*-------------------------------------------------------------------------- 92 | get/sync/call 93 | ------------------------------------------------------------------------*/ 94 | /** 95 | * Create get accessors 96 | * 97 | * Note type definitions do not support wildcards 98 | */ 99 | export function get( 100 | path: string | object, 101 | props: Array | { [K in keyof T]: string } 102 | ): { [K in keyof T]: { get: GetAccessor } }; 103 | export function get( 104 | path: string | object, 105 | props?: string[] | object 106 | ): { get: GetAccessor }; 107 | 108 | /** 109 | * Create get/set accessors 110 | * 111 | * Note type definitions do not support wildcards 112 | */ 113 | export function sync( 114 | path: string | object, 115 | props: Array | { [K in keyof T]: string } 116 | ): { [K in keyof T]: { get: GetAccessor; set: SetAccessor } }; 117 | export function sync( 118 | path: string | object, 119 | props?: string[] | object 120 | ): { get: GetAccessor; set: SetAccessor }; 121 | 122 | export function call( 123 | path: string | object, 124 | props?: string[] | object 125 | ): (payload: any) => any | Promise; 126 | 127 | /*-------------------------------------------------------------------------- 128 | Property Decorators 129 | ------------------------------------------------------------------------*/ 130 | export function Get(path: string): ReturnType; 131 | 132 | export function Sync(path: string): ReturnType; 133 | 134 | export function Call(path: string): ReturnType; 135 | 136 | /*-------------------------------------------------------------------------- 137 | commit 138 | ------------------------------------------------------------------------*/ 139 | // Copied from vuex types. 140 | export function commit( 141 | type: string, 142 | payload?: any, 143 | options?: CommitOptions 144 | ): void; 145 | export function commit

( 146 | payloadWithType: P, 147 | options?: CommitOptions 148 | ): void; 149 | 150 | /*-------------------------------------------------------------------------- 151 | dispatch 152 | ------------------------------------------------------------------------*/ 153 | // Copied from vuex types. 154 | export function dispatch( 155 | type: string, 156 | payload?: any, 157 | options?: DispatchOptions 158 | ): Promise; 159 | export function dispatch

( 160 | payloadWithType: P, 161 | options?: DispatchOptions 162 | ): Promise; 163 | 164 | /*-------------------------------------------------------------------------- 165 | registerModule 166 | ------------------------------------------------------------------------*/ 167 | export function registerModule( 168 | path: string | string[], 169 | module: object, 170 | callback: (...args: any[]) => any, // TODO: Needs refinement. 171 | options: object 172 | ): object; // TODO: Needs more details from module author. 173 | -------------------------------------------------------------------------------- /types/test/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { GetterTree, ActionTree, MutationTree } from "vuex"; 3 | import pathify, { make, get, sync } from "../index"; 4 | 5 | interface RootState { 6 | name: string; 7 | } 8 | 9 | const state: RootState = { name: "name" }; 10 | 11 | const getters: GetterTree = { 12 | ...make.getters(state) 13 | }; 14 | 15 | const mutations: MutationTree = { 16 | ...make.mutations(state) 17 | }; 18 | 19 | const actions: ActionTree = { 20 | ...make.actions(state) 21 | }; 22 | 23 | const plugin = pathify.plugin; 24 | 25 | Vue.extend({ 26 | computed: { 27 | foo: get('foo'), 28 | bar: get('bar'), 29 | baz: get<{ type: 'baz' }>('baz') 30 | }, 31 | created () { 32 | this.foo // any 33 | this.bar // string 34 | this.baz // object 35 | this.baz.type 36 | } 37 | }) 38 | 39 | Vue.extend({ 40 | computed: get<{ 41 | search: string, 42 | items: any[] 43 | }>('products', [ 44 | 'search', 45 | 'items', 46 | ]), 47 | created () { 48 | this.search 49 | this.items 50 | } 51 | }) 52 | 53 | Vue.extend({ 54 | computed: { 55 | ...sync<{ 56 | sortOrder: 'asc' | 'desc', 57 | sortKey: string 58 | }>('products/filters@sort', { 59 | sortOrder: 'order', 60 | sortKey: 'key', 61 | }) 62 | }, 63 | created () { 64 | this.sortOrder 65 | this.sortKey 66 | } 67 | }) 68 | -------------------------------------------------------------------------------- /types/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "lib": ["es5", "dom", "es2015.promise", "es2015.core"], 7 | "strict": true, 8 | "noEmit": true 9 | }, 10 | "include": ["*.ts", "../*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "lib": ["es5", "dom", "es2015.promise"], 7 | "strict": true, 8 | "noEmit": true 9 | }, 10 | "include": ["*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /types/vue.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extends interface in Vue.js 3 | * If we don't augment Vue, then vuex is not augmented either. 4 | */ 5 | import Vue from "vue"; 6 | 7 | declare module "vue/types/vue" { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /types/vuex.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extends interface of vuex 3 | */ 4 | 5 | import vuex from "vuex"; 6 | 7 | declare module "vuex" { 8 | // Here should be defined added features to vuex and stores 9 | // import vuex, { Store } from "vuex"; 10 | 11 | // Signatures from https://davestewart.github.io/vuex-pathify/#/api/accessors 12 | interface Store { 13 | get: (path: string, ...args: any[]) => any; 14 | set: (path: string, value: any) => any | Promise; 15 | copy: (path: string) => any; 16 | } 17 | } 18 | --------------------------------------------------------------------------------