# Luca's thoughts on Vuex-Cloud
I have split up my thoughts and ideas per package we'd be providing. For each package I have written down how I see the possible available actions, setup, helper functions and features could look like.
I think it's best we discuss this in this order:
1. First try to keep an open mind and read through the entire document once. (some things might get more clear once you read through everything once)
2. Then we can call to talk about your general opinions and the direction I'm thinking about.
3. Then we can go over the document top to bottom and (a) decide on a bunch of things together and (b) you can tell me what where you don't agree or have other specific ideas.
# @vuex-cloud/actions (core actions)
## Actions
If we copy the Firebase Firestore SDK:
```javascript
dispatch('m/update', data)
// requires ID; merges with existing data
dispatch('m/set', data)
// without ID: create
// with non-existing ID: create with that custom ID
// with existing ID: update but overwrite entire object
dispatch('m/delete', id)
```
Error handling
- Each action returns a promise (as per default in vuex)
- resolves the `id` if the action was successful
- without anything this is immidiately
- with firebase enabled this is after the batch update (1sec)
- with local storage options enabled this is after saving locally
- rejects with the error if the action was unsuccessful
### Things to decide (Actions):
1. do we need an extra `insert` (or `create` or `add`) action?
2. naming: `update` or `patch` or `modify`
3. naming: `delete` or `remove` or `destroy`
4. separate "batch" actions or allow each action to also be passed an array for batch actions
> I'm thinking we don't need an extra action
5. how to organise the docs inside a vuex-module
```javascript
const data = {/* the doc(s) */}
const local = {/* the dev's other state props */}
// state A.
export default { data, ...local }
// state B.
export default { ...data, local }
// state C.
export default { data, local }
// state D.
// choose A or B or C
// state E.
// choose A or B or C with any custom prop names
```
> I personally prefer 5A.
6. Delete implementation:
Basic:
```javascript
dispatch('m/delete', 'abc123')
// this deletes 'abc123' from the "data" prop of the state (see above)
```
Nested props:
```javascript
// deleting nested props A: (like Firebase SDK)
dispatch('m/delete', 'abc123.nested.prop')
// deleting nested props B:
dispatch('m/deleteProp', {id: 'abc123', path: 'nested.prop'})
// deleting nested props C:
dispatch('m/deleteProp', {id: 'abc123', propPath: 'nested.prop'})
```
> I personally prefer 6A.
7. Array helpers?
```javascript
// A. a-la Firebase SDK
dispatch('m/update', {id: 'abc123', nested: {arr: arrayUnion('a')}})
dispatch('m/update', {id: 'abc123', nested: {arr: arrayRemove('a')}})
// B. a-la JavaScript
dispatch('m/update', {id: 'abc123', nested: {arr: push('a')}})
dispatch('m/update', {id: 'abc123', nested: {arr: unshift('a')}})
dispatch('m/update', {id: 'abc123', nested: {arr: pop()}})
dispatch('m/update', {id: 'abc123', nested: {arr: shift()}})
dispatch('m/update', {id: 'abc123', nested: {arr: splice(0, 1, newVal)}})
```
> I think I prefer 7B. because it's just JavaScript
## Hooks
```javascript
{
hookBeforeSet: (updateState, doc, store) => updateState(doc),
hookBeforeUpdate: (updateState, doc, store) => updateState(doc),
hookBeforeDelete: (updateState, id, store) => updateState(id),
}
```
### Things to decide (Hooks):
1. Do we also need hooks after local actions, or other hooks "before sync"?
```javascript
{
hookAfterSet: (doc, store) => {},
hookAfterUpdate: (doc, store) => {},
hookAfterDelete: (id, store) => {},
}
```
2. Where to define hooks, see Module setup below.
## Module Setup
```javascript
import { set, update, delete as del } from '@vuex-cloud/actions'
export default {
state: {
data: {},
// dev's other state props
},
actions: {
set,
update,
delete: del,
// dev's other actions
},
}
```
The absolute benefit of manually importing these actions in every module like so is that a developer gets documentation information (JSDoc) just by hovering over them (at least in VSCode, not sure about others).
When a team uses vuex-cloud and one dev needs to update the UI, but doesn't know what the action "set" does, his first instinct is to go look for that action in the module's actions. There he can see it and get documentation without having to go to the vuex-cloud documentation website.
### Things to decide (Setup):
1. Since `delete` is a [preserved keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), maybe we should use another name like `remove`...
2. Do we (and how) do we make our actions dispatch mutations
It must be noted that they've already reported long ago **they'd get rid of mutations** in vuex in the future.
> I'm not a fan of using mutations in the first place. (never had any benefit from the mutation history in my life) But if we don't use mutations, the dev cannot have his vuex to be "strict" compliant. (I'm personally not a fan of strict either.)
If we do go for using mutations, maybe we can have the mutations automatically added like so:
```javascript
import { set, update, delete as del, vuexCloudMutations } from '@vuex-cloud/actions'
export default {
state: {
data: {},
// dev's other state props
},
actions: {
set,
update,
delete: del,
// dev's other actions
},
mutations: {
...vuexCloudMutations,
},
}
```
Because not important for the dev to know / see specific mutations.
Another option would be to directly mutate the state (and thus not allow strict mode). If we have test coverage there shouldn't be anything wrong with this.
3. Where to add hooks?
```javascript
import { set, update } from '@vuex-cloud/actions'
export default {
state: {
data: {},
// dev's other state props
_vuexCloudConfig: {
hooks: {},
},
},
actions: {
set,
update,
},
}
```
This is just an example. Also see next chapter:
## Default Values
> Big fan. ;)
`createdAt` / `createdBy` should be fields the dev adds manually to the Default Values.
```javascript
import { set, update } from '@vuex-cloud/actions'
export default {
state: {
data: {},
_vuexCloudConfig: {
hooks: {},
defaultValues: () => ({
createdAt: new Date(),
}),
},
},
actions: {
set,
update,
},
}
```
#### New feature: Interactive default values
An example of interactive defaultValues, eg. adding "!" behind a certain value by default.
```javascript
export default {
state: {
_vuexCloudConfig: {
defaultValues: (payload) => ({
createdAt: new Date(),
name: (payload) => `${payload.name}!`,
})
},
},
// ...
}
```
# @vuex-cloud/plugin-actions (extra actions, not so core)
## Actions
1. Increment
```javascript
// A. via helper fn (current v-e-f implementation)
dispatch('m/update', {id, some: {nested: {prop: increment(10)}}})
// B. via new action to import
dispatch('m/increment', {id, path, count})
```
> I prefer 1A. I think.
2. Duplicate
```javascript
// A. via new action to import (current v-e-f implementation)
dispatch('m/duplicate', id) // or id[]
// resolve into the `newId` or a map: {[oldId]: newId}
// B. via helper fn
dispatch('m/set', duplicate(id, id2))
```
> Kinda like 2B. :-O
# @vuex-cloud/firebase-firestore
While this happens behind the scene's, there are some features I want to keep from v-e-f.
## Setup
The most important thing will be how to add the setup to the vuex module.
We won't need v-e-f `moduleName: 'myModule'` anymore, because we add the module to vuex as a regular module
### Implementation example 1
```javascript
import { linkToFirebase } from '@vuex-cloud/firebase-firestore'
import { set, update } from '@vuex-cloud/actions'
export default linkToFirebase({
// first param is the setup
firestorePath: 'myDocs',
firestoreRefType: 'collection', // or 'doc'
}, {
// second param is the module
state: {
data: {},
_vuexCloudConfig: {
hooks: {},
},
},
actions: {
set,
update,
},
})
```
### Implementation example 2
```javascript
import { linkToFirebase } from '@vuex-cloud/firebase-firestore'
import { set, update } from '@vuex-cloud/actions'
export default linkToFirebase({
// second param is the module
state: {
data: {},
_vuexCloudConfig: {
hooks: {},
// here is firestore config:
firestore: {
path: 'myDocs',
},
},
},
actions: {
set,
update,
},
})
```
### Things to decide (Setup)
1. Do we use a separate first parameter OR add the config to `_vuexCloudConfig`
2. We can derive wether or not it's a doc or collection by the firestorePath, which is always `collection/doc/collection/doc/collection` and so forth. But should we?
3. We need to think about `statePropName: 'data'` in relation to what we decided for @vuex-cloud/actions
4. We could also add an object called `firestore` to the `_vuexCloudConfig` instead. But should we?
## Read Actions
```javascript
import { linkToFirebase, get, onSnapshot } from '@vuex-cloud/firebase-firestore'
export default linkToFirebase({
state: {
data: {},
_vuexCloudConfig: {
firestore: {
path: 'myDocs/{userId}/collection',
},
},
},
actions: {
get,
onSnapshot,
},
})
```
I suggest we stay as close to the Firebase SDK as possible. I will now go over each action with a proposition for an implementation similar to the Firebase SDK.
[Firebase docs: get](https://firebase.google.com/docs/firestore/query-data/get-data#get_a_document)
[Firebase docs: onSnapshot](https://firebase.google.com/docs/firestore/query-data/listen)
### Implementation idea 1
1. get
```javascript
dispatch('m/get') // previously fetchAndAdd
dispatch('m/get', {where})
```
If firestorePath is a
- 'collection': it will retrieve & add the entire collection
- 'doc': it will retrieve & add that doc
2. get (by doc id)
```javascript
dispatch('m/get', '123abc') // previously fetchById
// same as:
dispatch('m/get', {where: ['id', '==', '123abc']})
// however, the latter is only possible when the id is saved on the doc
// will also be slower as the former
```
You can get a specific doc only when it's a 'collection'.
3. onSnapshot
```javascript
dispatch('m/onSnapshot') // previously openDBChannel
dispatch('m/onSnapshot', {where})
```
### Implementation idea 2 (even closer to Firebase SDK)
1. get or onSnapshot
```javascript
const ref = getters['m/firestoreRef']
// this is your reference of your module, just like this:
// - Firebase.firestore().collection(path)
// - Firebase.firestore().doc(path)
dispatch('m/get', ref.where().limit())
dispatch('m/onSnapshot', ref.where())
```
This method uses the native where and limit functions etc., the action itself will receive the ref and actually trigger the `get()` or `onSnapshot()` on it, and handle the result.
### Implementation idea 3 (a syntax we could use for any storage system)
If we change `get` and `onSnapshot` for `read` and `stream` we could use the same syntax for any storage solution, and still keep the rest the same as implementation idea 1 or 2 above.
Eg.
```javascript
dispatch('m/read')
dispatch('m/read', {where, limit})
dispatch('m/stream', {where})
// or
dispatch('m/read', ref.where().limit())
dispatch('m/stream', ref.where())
```
### Extra features to decide on: ...
## Hooks
```javascript
{
hookBeforeServerAdd: (updateStore, doc, id, store) => updateStore(doc),
hookBeforeServerModify: (updateStore, doc, id, store) => updateStore(doc),
hookBeforeServerRemove: (updateStore, doc, id, store) => updateStore(doc),
hookAfterServerAdd: (doc, id, store) => {},
hookAfterServerModify: (doc, id, store) => {},
hookAfterServerRemove: (doc, id, store) => {},
}
```
We need to think about these names...
`'added'`, `'modified'`, `'removed'` are the [terms used in the Firebase SDK](https://firebase.google.com/docs/firestore/query-data/listen#view_changes_between_snapshots).
## Automatic Batching per x ms
Automatic or not...
It can be a blessing or a curse. Instead of making this the default, we could only do this when the user sets a "syncDebounceMs" to a `number`. Eg. 1000 = 1 sec debounce between syncs and then makes a single batched update.
Currently in v-e-f it's set to 1000ms by default, but can be modified. See [v-e-f docs](https://mesqueeb.github.io/vuex-easy-firestore/extra-features.html#custom-sync-debounce-duration).
## Manual fetch handling
See [v-e-f docs](https://mesqueeb.github.io/vuex-easy-firestore/query-data.html#manual-fetch-handling). I think maybe we don't need this at all. The dev can implement his own read calls if he wants to do this.
## Fillables and guard
Can be similar to v-e-f imo. See [v-e-f docs](https://mesqueeb.github.io/vuex-easy-firestore/extra-features.html#fillables-and-guard).
## Firestore Timestamp conversion
Can be similar to v-e-f imo. But instead of the weird syntax I have currently, allow for an array in the config like so: `convertTimestampOfProps: ['createdAt']`
See [v-e-f docs](https://mesqueeb.github.io/vuex-easy-firestore/extra-features.html#firestore-timestamp-conversion).
## Variables for firestorePath or filters
This is in my opinion the **most important change** from v-e-f. (See [v-e-f docs](https://mesqueeb.github.io/vuex-easy-firestore/extra-features.html#variables-for-firestorepath-or-filters) for a refresher)
Since vuex-modules are not added by plugin, but as regular vuex modules, the dev can utilise Vuex's [**Dynamic Module Registration**](https://vuex.vuejs.org/guide/modules.html#dynamic-module-registration) (which is currently not possible).
Dynamic Module Registration allows for the dev to just provide a different firestore path and register it besides the other modules. He can also decide to overwrite another module dynamically. (eg. a 'userData' module when a new user logs in.)
We just need to provide good enough documentation on
1. how to do this
2. how to make sure that the browser memory is cleared of data when the module is overwritten
3. how to make sure the onSnapshot listeners are closed
4. perhaps provide some helper function that can be utilised to do this more easily.
## onSnapshot listener management
When all open onSnapshot listeners are related to a user, they all need to be cleared when he signs out. We need to provide some helpers for this.
# @vuex-cloud/file-storage (local persistence)
I could use this for cordova apps of mine that don't use firebase.
And / or to save data before the user creates a profile with Firebase Auth.
Unlike indexedDb it never gets deleted automatically by iOS etc.
> I personally don't need this any time soon though.
# @vuex-cloud/websockets
Custom websockets could be set up with any API via some helper functions. Then the dev could use the same `read` and `stream` actions like Firestore.
> I personally don't need this, just an idea.
# @vuex-cloud/firebase-auth
There's definitely things we can make into a library. I've been thinking about this a lot. Actions like `signIn`, `signOut`, or actions to subscribe new users via email etc.
Also settings like "prevent new users signup" etc.
> I need this rather soonish actually for a client project. :smile:
# @vuex-cloud/firebase-custom-claims
For a user role-based system. It's the de-facto method to mix firestore rules with user roles, but it's a pain in the butt to learn...
I'm sure some things can be made into an easy to use library.
> I also need this in about 6 months. :sweat_smile:
# Other thoughts
## Benefits from using actions + any storage option
- streamlined syntax no matter the storage
- optimistic UI
## Similar services
- [LokiJS](http://techfort.github.io/LokiJS/)
Very badly maintained, but I managed to use this with file storage on a cordova app before.
> I found them because Loki is the name of my son. XD
## Other stuff
#### 1. I want vuex-cloud to use my/our own helper functions as much as possible (the `-anything` series + more to follow)
- because you can keep them as small as possible to focus just on the functionality you need
- less dangerous, because you never know when an external library owner "gives" his repo away because he doesn't want to maintain it anymore & then maliscious code gets added (based on true facts :P)
- more productive because you can immediately add / change the helper to our own needs
- writing our own tests makes sure that it will work as we intend when implementing in our library
#### 2. About ownership, reputation, honour, donations, contributions, etc.
I really want to know more about your stance on things like: ownership, reputation, honour, donations, contributions, etc.
I mainly started Vuex Easy Firestore to just use it myself. However, after I started, I put in a lot of **blood, sweat and tears** because of the possibility of getting **donations**, and even more importantly: getting a **good reputation**. V-e-f has since then landed me a couple of **freelance jobs** and I was even able to make some friends.
For me, on one side, there's a sort of honour from being the "owner" of a popular package, when it starts with: username/package. (eg. mesqueeb/vuex-easy-firestore) Also, because of the benefits I already enjoyed so far, just continueing v-e-f on my own is certainly not a bad option for me.
By making an **organisation** out of @vuex-cloud it's less obvious who's the creators/owners. However, there are still benefits from this approach as well. I believe personally, doing this as organisation, will in the long run allow me to grow more, and meet even more great people. This is a big plus for me.
I'm not 100% sure what *your* inner wishes and desires are, so I'd love to hear them in all honesty. Since the name "vuex-cloud" as well as the idea to split the library in pieces came from you: Do you want to be seen as the creator?
What's your main gain in this? Do you wanna build the package just because you need it? Do you enjoy the reputation? Do you anticipate donations? Do you wish this will get you more jobs? I'd love to hear all about this because it'll be easier for us to work on this together if we know where we both stand. :wink:
I think either way it might be cool if we create a "contributors" page on our documentation website with automatic aggregation (pulled from Github) of how much each contributor contributed. Then we can also have a sponsor button behind the top 3~5 contributors or something.
I guess in that case, in the beginning it'll just be you and me fighting for most commits though... lol :laughing:
#### 3. About how to call ourselves in regard to vuex-cloud
I can currently say: "I'm the creator of vuex-easy-firestore."
My question for you is how you would feel if I talk about vuex-cloud in the following ways:
- "I created vuex-cloud" (Or would you rather have me always emphasise "with others" or "with Louis")
- "Vuex-cloud was born from my library Vuex-Easy-Firestore"
- "Vuex-cloud is Vuex-Easy-Firestore 2.0"
Are there any things you would rather have me not say, or don't you care so much?
#### 4. About the practicalities
On the practical side of things: I personally would love to work on the groundwork for vuex-cloud, and I'm sure that you would want to do so as well.
I'm not sure how we can work on this together without being in each other's hair. XD Do you have any experience and/or working methods on doing something like this together?
#### 5. About package naming
I believe we should use package names that are as short as possible. Eg. vue uses '@vue/core'. So we could use names like '@vuex-cloud/actions' to provide the core actions. Etc.
- @vuex-cloud/actions
- @vuex-cloud/websockets
- @vuex-cloud/file-storage
- @vuex-cloud/firebase-firestore
- @vuex-cloud/firebase-auth
- @vuex-cloud/firebase-custom-claims
#### 6. About documentation language
I'd say UK English over US English, because I like it so much better. XD
Eg. honour > honor, colour > color, centralise > centralize, centre > center, organise > organize, analyse > analyze, analogue > analog, catalogue > catalog