Skip to content

Commit

Permalink
Merge pull request #1 from mesqueeb/dev
Browse files Browse the repository at this point in the history
🏝 Alpha 3
  • Loading branch information
mesqueeb authored Jun 21, 2018
2 parents 07e9948 + 4093ef9 commit 4d0536b
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 156 deletions.
91 changes: 25 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Includes:

Todo:

- Auto sync with watchers
- Refinements
- Documentation

### Table of contents
Expand All @@ -37,66 +37,31 @@ Get all the firebase boilerplate installed for you in one vuex module.
The configuration as seen below is how you set up vuex-easy-firestore. This is to be repeated for each firestore collection you want to sync.

```js
import autoFirestore from 'vuex-easy-firestore'
import createEasyFirestore from 'vuex-easy-firestore'
const config = {
// Your configuration!
// SEE `module/defaultConfig` for examples
moduleNameSpace: 'user/favourites',
// this is the vuex module path that will be created
docsStateProp: 'docs',
// this is the state property where your docs will end up inside the module
firestorePath: 'users/{userId}/favourites',
// this is the firestore collection path to your documents. You can use `{userId}` which will be replaced with `Firebase.auth().uid`
// SEE `src/module/defaultConfig` for more!
}
// do the magic 🧙🏻‍♂️
const firestore = autoFirestore(config)
const easyFirestore = createEasyFirestore(config)
// and include as module in your vuex store:
store: {
modules: {
firestore
}
// ... your store
plugins: [easyFirestore]
}
```

### Config options

About the config file, better documentation will follow. For now see `module/defaultConfig`

<!-- - `vuexstorePath: ''` must be relative to rootState
- `firestorePath: ''` -->
<!-- - mapType: 'collection', // 'collection' only ('doc' not integrated yet) -->
<!-- - type: '2way', // '2way' only ('1way' not yet integrated) -->
<!-- - `sync.where: []`
- `sync.orderBy: []`
- `sync.defaultValues: {}`
You HAVE to set all fields you want to be reactive on beforehand!
These values are only set when you have items who don't have the props defined in defaultValues upon retrieval
These default values will be merged with a reverse Object.assign on retrieved documents
- `sync.added: syncHookFn`
- `sync.modified: syncHookFn`
- `sync.removed: syncHookFn` -->

<!-- synchookFn example:
```js
/**
* A function executed during the 2 way sync when docs are added/modified/deleted. NEEDS TO EXECUTE FIRST PARAM! You can use this function to do a conditional check on the documents to decide if/when to execute the store update.
*
* @param {function} storeUpdateFn this is the function that will make changes to your vuex store. Takes no params.
* @param {object} store the store for usage with `store.dispatch`, `store.commit`, `store.getters` etc.
* @param {string} id the doc id returned in `change.doc.id` (see firestore documentation for more info)
* @param {object} doc the doc returned in `change.doc.data()` (see firestore documentation for more info)
* @param {string} source of the change. Can be 'local' or 'server'
*/
function syncHook (storeUpdateFn, store, id, doc, source, change) {
// throw error if you want to stop the document in your store from being modified
// do some stuff
storeUpdateFn()
// do some stuff
}
``` -->
About the config file, better documentation will follow. For now see `src/module/defaultConfig` for all possibilities.

<!-- - `fetch.docLimit: 50` // defaults to 50
- `insert.checkCondition (doc, storeRef) { return (params == 'something') }`
- `insert.fillables: []`
- `insert.guard: []`
- `patch.checkCondition (id, fields, storeRef) { return (params == 'something') }`
- `patch.fillables: []`
- `patch.guard: []`
- `delete.checkCondition (id, storeRef) { return (params == 'something') }` -->
You can also add other state/getters/mutations/actions to the module that will be generated. See [Custom state/getters/mutations/actions](#custom-stategettersmutationsactions) for details.

## Usage

Expand All @@ -105,37 +70,37 @@ function syncHook (storeUpdateFn, store, id, doc, source, change) {
You need to dispatch the following action once to open the channel to your firestore.

```js
dispatch('firestore/openDBChannel')
dispatch('user/favourites/openDBChannel')
.then(console.log)
.catch(console.error)
```

For now any changes need to be notified manually. See 'Editing' below.

Automatic 2 way sync is a WIP.
To automatically edit your vuex store & have firebase always in sync you just need to use the actions that were set up for you.

### Editing

The many actions you get for free:
With these 4 actions below you can edit the docs in your vuex module.
Anything you change will be automaticall changed in firestore as well!

```js
dispatch('firestore/patch', {id = '', ids = [], field = '', fields = []})
dispatch('firestore/delete', {id = '', ids = []})
dispatch('firestore/insert', {item = {}, items = []})
dispatch('user/favourites/set', doc) // will dispatch `patch` OR `insert` automatically
dispatch('user/favourites/patch', doc)
dispatch('user/favourites/insert', doc)
dispatch('user/favourites/delete', id)
```

All changes through the functions above work with batches.
With just the commands above you have complete in-sync vuex store & firestore!

### Fetching
```js
// Fetch docs
// @params {array} whereFilters an array of arrays with the filters you want. eg. `[['field', '==', false], ...]`
// @params {array} orderBy the params of the firebase collection().orderBy() eg. `['created_date']`
// @returns the docs
dispatch('firestore/fetch', {whereFilters = [], orderBy = []})
dispatch('user/favourites/fetch', {whereFilters = [], orderBy = []})
```

See `module/actions` for a full list.
You only ever need to use the 5 actions above. You can look at `src/module/actions` for what's more under the hood.

### Custom state/getters/mutations/actions

Expand All @@ -149,9 +114,3 @@ const vuexEasyFirestoreConfig = {
actions: {}, // extra actions
}
```

<!-- ## Build from source
```bash
npm run build
``` -->
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vuex-easy-firestore",
"version": "0.0.0-alpha1",
"version": "0.0.0-alpha3",
"description": "Easy coupling of firestore and a vuex module. 2-way sync with 0 boilerplate!",
"main": "/src/index.js",
"scripts": {
Expand Down Expand Up @@ -28,7 +28,7 @@
"firebase": "^5.0.4",
"is-what": "^1.0.1",
"nanoclone": "^0.2.1",
"vuex-easy-access": "^1.0.9"
"vuex-easy-access": "^1.0.10"
},
"devDependencies": {
"babel-core": "^6.26.3",
Expand Down
54 changes: 26 additions & 28 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
// store
import iniState from './module/state'
import iniMutations from './module/mutations'
import iniActions from './module/actions'
import iniGetters from './module/getters'
// import { getKeysFromPath } from 'vuex-easy-access'
import { getKeysFromPath } from './utils/temp-vuex-easy-access'
import { isArray } from 'is-what'
import iniModule from './module/index'

let conf = {state: null, mutations: null, actions: null, getters: null}
// I don't know why getKeysFromPath is UNDEFINED WTF

/**
* A function that returns a vuex module object with seamless 2-way sync for firestore.
*
* @param {object} userConfig Takes a config object as per ...
* @returns {object} the module ready to be included in your vuex store
*/
export default function (userConfig) {
Object.assign(conf, userConfig)
const userState = conf.state
const userMutations = conf.mutations
const userActions = conf.actions
const userGetters = conf.getters
delete conf.state
delete conf.mutations
delete conf.actions
delete conf.getters
return {
namespaced: true,
state: iniState(userState, conf),
mutations: iniMutations(userMutations),
actions: iniActions(userActions),
getters: iniGetters(userGetters)
export default function createEasyFirestore (userConfig) {
return store => {
// Get an array of config files
if (!isArray(userConfig)) userConfig = [userConfig]
// Create a module for each config file
userConfig.forEach(config => {
const moduleNameSpace = getKeysFromPath(config.moduleNameSpace)
store.registerModule(moduleNameSpace, iniModule(config))
})
store.setDoc = (path, payload) => {
return store.dispatch(path + '/setDoc', payload)
}
store.insertDoc = (path, payload) => {
return store.dispatch(path + '/insertDoc', payload)
}
store.patchDoc = (path, payload) => {
return store.dispatch(path + '/patchDoc', payload)
}
store.deleteDoc = (path, payload) => {
return store.dispatch(path + '/deleteDoc', payload)
}
}
}
74 changes: 41 additions & 33 deletions src/module/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import setDefaultValues from '../utils/setDefaultValues'
import startDebounce from '../utils/debounceHelper'

const actions = {
patch (
patchDoc (
{state, getters, commit, dispatch},
{id = '', ids = [], field = '', fields = []} = {ids: [], fields: []}
) {
Expand All @@ -31,12 +31,10 @@ const actions = {
// 3. Create or refresh debounce
return dispatch('handleSyncStackDebounce')
},
delete ({state, getters, commit, dispatch},
{id = '', ids = []} = {ids: []}) {
deleteDoc ({state, getters, commit, dispatch},
ids = []) {
// 0. payload correction (only arrays)
if (!isArray(ids)) return console.log('ids needs to be an array')
if (!isString(id)) return console.log('id needs to be a string')
if (id) ids.push(id)
if (!isArray(ids)) ids = [ids]

// 1. Prepare for patching
const syncStackIds = getters.prepareForDeletion(ids)
Expand All @@ -49,17 +47,16 @@ const actions = {
// 3. Create or refresh debounce
return dispatch('handleSyncStackDebounce')
},
insert ({state, getters, commit, dispatch},
{item, items = []} = {items: []}) {
insertDoc ({state, getters, commit, dispatch},
docs = []) {
// 0. payload correction (only arrays)
if (!isArray(items)) return console.log('items needs to be an array')
if (item) items.push(item)
if (!isArray(docs)) docs = [docs]

// 1. Prepare for patching
const syncStackItems = getters.prepareForInsert(items)
const syncStack = getters.prepareForInsert(docs)

// 2. Push to syncStack
const inserts = state.syncStack.inserts.concat(syncStackItems)
const inserts = state.syncStack.inserts.concat(syncStack)
commit('SET_SYNCSTACK.INSERTS', inserts)

// 3. Create or refresh debounce
Expand Down Expand Up @@ -148,7 +145,7 @@ const actions = {
}
// Add to batch
inserts.forEach(item => {
let newRef = getters.dbRef.doc()
let newRef = getters.dbRef.doc(item.id)
batch.set(newRef, item)
})
// Commit the batch:
Expand All @@ -157,7 +154,7 @@ const actions = {
// ${deletions.length} deletions,
// ${inserts.length} inserts`
// )
dispatch('startPatching')
dispatch('_startPatching')
commit('SET_SYNCSTACK.DEBOUNCETIMER', null)
await batch.commit()
.then(res => {
Expand All @@ -170,11 +167,11 @@ const actions = {
+ state.syncStack.deletions.length
+ state.syncStack.inserts.length
if (remainingSyncStack) { dispatch('batchSync') }
dispatch('stopPatching')
dispatch('_stopPatching')

// // Fetch the item if it was added as an Archived item:
// if (item.archived) {
// getters.dbRef.doc(res.id).get()
// get_ters.dbRef.doc(res.id).get()
// .then(doc => {
// let tempId = doc.data().id
// let id = doc.id
Expand Down Expand Up @@ -248,16 +245,17 @@ const actions = {
})
})
},
dbUpdate ({dispatch}, {change, id, doc}) {
serverUpdate ({commit}, {change, id, doc = {}}) {
doc.id = id
switch (change) {
case 'added':
dispatch('SET_DOC', {id, doc})
commit('INSERT_DOC', doc)
break
case 'modified':
dispatch('OVERWRITE_DOC', {id, doc})
commit('PATCH_DOC', doc)
break
case 'removed':
dispatch('DELETE_DOC', {id})
commit('DELETE_DOC', id)
break
}
},
Expand All @@ -275,7 +273,6 @@ const actions = {
dbRef
.onSnapshot(querySnapshot => {
let source = querySnapshot.metadata.hasPendingWrites ? 'local' : 'server'
console.log(`found ${querySnapshot.docs.length} documents`)
querySnapshot.docChanges().forEach(change => {
// Don't do anything for local modifications & removals
if (source === 'local' &&
Expand All @@ -287,8 +284,8 @@ const actions = {
const doc = (change.type === 'added')
? setDefaultValues(change.doc.data(), state.sync.defaultValues)
: change.doc.data()
// prepare dbUpdate action
function storeUpdateFn () { return dispatch('dbUpdate', {change: change.type, id, doc}) }
// prepare serverUpdate to DB
function storeUpdateFn () { return dispatch('serverUpdate', {change: change.type, id, doc}) }
// get user set sync hook function
const syncHookFn = state.sync[change.type]
if (syncHookFn) {
Expand All @@ -304,22 +301,33 @@ const actions = {
})
})
},
SET_DOC ({getters}, {id, doc}) {
this._vm.$set(getters.storeRef, id, doc)
set ({commit, dispatch, getters, state}, doc) {
if (!doc) return
if (!doc.id || !state[state.docsStateProp][doc.id]) {
return dispatch('insert', doc)
}
return dispatch('patch', doc)
},
insert ({commit, dispatch, getters}, doc) {
if (!doc) return
if (!doc.id) doc.id = getters.dbRef.doc().id
commit('INSERT_DOC', doc)
return dispatch('insertDoc', doc)
},
OVERWRITE_DOC ({getters}, {id, doc}) {
this._vm.$set(getters.storeRef, id, Object.assign(
getters.storeRef[id], doc
))
patch ({commit, dispatch, getters}, doc) {
if (!doc || !doc.id) return
commit('PATCH_DOC', doc)
return dispatch('patchDoc', {id: doc.id, fields: Object.keys(doc)})
},
DELETE_DOC ({getters}, {id}) {
this._vm.$delete(getters.storeRef, id)
delete ({commit, dispatch, getters}, id) {
commit('DELETE_DOC', id)
return dispatch('deleteDoc', id)
},
stopPatching ({state, commit}) {
_stopPatching ({state, commit}) {
if (state.stopPatchingTimeout) { clearTimeout(state.stopPatchingTimeout) }
state.stopPatchingTimeout = setTimeout(_ => { commit('SET_PATCHING', false) }, 300)
},
startPatching ({state, commit}) {
_startPatching ({state, commit}) {
if (state.stopPatchingTimeout) { clearTimeout(state.stopPatchingTimeout) }
commit('SET_PATCHING', true)
}
Expand Down
Loading

0 comments on commit 4d0536b

Please sign in to comment.