7 |
8 | [GitHub](https://github.com/davestewart/vuex-pathify)
9 | [Get Started](#home)
10 |
11 |
12 | 
--------------------------------------------------------------------------------
/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 | 
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]
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]
25 |
26 | Use the navigation to load the demos, then click the green buttons at the top to edit the associated files:
27 |
28 | 
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