Skip to content

Commit

Permalink
WIP doc integration
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed Jun 27, 2018
1 parent 4093ef9 commit eaf0a05
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 73 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const config = {
}
// do the magic 🧙🏻‍♂️
const easyFirestore = createEasyFirestore(config)
// and include as module in your vuex store:
// and include as plugin in your vuex store:
store: {
// ... your store
plugins: [easyFirestore]
Expand Down
14 changes: 8 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Firebase from 'firebase/app'
import 'firebase/auth'
// import { getKeysFromPath } from 'vuex-easy-access'
import { getKeysFromPath } from './utils/temp-vuex-easy-access'
import { isArray } from 'is-what'
Expand All @@ -17,14 +19,14 @@ export default function createEasyFirestore (userConfig) {
store.setDoc = (path, payload) => {
return store.dispatch(path + '/setDoc', payload)
}
store.insertDoc = (path, payload) => {
return store.dispatch(path + '/insertDoc', payload)
store.insert = (path, payload) => {
return store.dispatch(path + '/insert', payload)
}
store.patchDoc = (path, payload) => {
return store.dispatch(path + '/patchDoc', payload)
store.patch = (path, payload) => {
return store.dispatch(path + '/patch', payload)
}
store.deleteDoc = (path, payload) => {
return store.dispatch(path + '/deleteDoc', payload)
store.delete = (path, payload) => {
return store.dispatch(path + '/delete', payload)
}
}
}
66 changes: 44 additions & 22 deletions src/module/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const actions = {
) {
// 0. payload correction (only arrays)
if (!isArray(ids) || !isArray(fields)) return console.log('ids, fields need to be arrays')
if (!isString(id) || !isString(field)) return console.log('id, field need to be strings')
if (!isString(field)) return console.log('field needs to be a string')
if (id) ids.push(id)
if (field) fields.push(field)

Expand Down Expand Up @@ -72,6 +72,7 @@ const actions = {
state.syncStack.debounceTimer.refresh()
},
async batchSync ({getters, commit, dispatch, state}) {
const collectionMode = getters.collectionMode
const dbRef = getters.dbRef
let batch = Firebase.firestore().batch()
let count = 0
Expand All @@ -96,11 +97,12 @@ const actions = {
updates = updatesOK
} else {
state.syncStack.updates = {}
count = updates.length
}
// Add to batch
updates.forEach(item => {
let id = item.id
let docRef = dbRef.doc(id)
let docRef = (collectionMode) ? dbRef.doc(id) : dbRef
let fields = item.fields
batch.update(docRef, fields)
})
Expand Down Expand Up @@ -251,28 +253,52 @@ const actions = {
case 'added':
commit('INSERT_DOC', doc)
break
case 'modified':
commit('PATCH_DOC', doc)
break
case 'removed':
commit('DELETE_DOC', id)
break
default:
commit('PATCH_DOC', doc)
break
}
},
openDBChannel ({getters, state, commit, dispatch}) {
const collectionMode = getters.collectionMode
let dbRef = getters.dbRef
// apply where filters and orderBy
state.sync.where.forEach(paramsArr => {
dbRef = dbRef.where(...paramsArr)
})
if (state.sync.orderBy.length) {
dbRef = dbRef.orderBy(...state.sync.orderBy)
if (state.firestoreRefType.toLowerCase() !== 'doc') {
state.sync.where.forEach(paramsArr => {
dbRef = dbRef.where(...paramsArr)
})
if (state.sync.orderBy.length) {
dbRef = dbRef.orderBy(...state.sync.orderBy)
}
}
// define handleDoc()
function handleDoc (change, id, doc, source) {
change = (!change) ? 'modified' : change.type
// define storeUpdateFn()
function storeUpdateFn () {
return dispatch('serverUpdate', {change, id, doc})
}
// get user set sync hook function
const syncHookFn = state.sync[change]
if (syncHookFn) {
syncHookFn(storeUpdateFn, this, id, doc, source)
} else {
storeUpdateFn()
}
}
// make a promise
return new Promise ((resolve, reject) => {
dbRef
.onSnapshot(querySnapshot => {
let source = querySnapshot.metadata.hasPendingWrites ? 'local' : 'server'
if (!collectionMode) {
const doc = setDefaultValues(querySnapshot.data(), state.sync.defaultValues)
if (source === 'local') return resolve()
handleDoc(null, null, doc, source)
return resolve()
}
querySnapshot.docChanges().forEach(change => {
// Don't do anything for local modifications & removals
if (source === 'local' &&
Expand All @@ -284,16 +310,8 @@ const actions = {
const doc = (change.type === 'added')
? setDefaultValues(change.doc.data(), state.sync.defaultValues)
: change.doc.data()
// 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) {
syncHookFn(storeUpdateFn, this, id, doc, source)
} else {
storeUpdateFn()
}
resolve()
handleDoc(change, id, doc, source)
return resolve()
})
}, error => {
commit('SET_PATCHING', 'error')
Expand All @@ -303,6 +321,9 @@ const actions = {
},
set ({commit, dispatch, getters, state}, doc) {
if (!doc) return
if (!getters.collectionMode) {
return dispatch('patch', doc)
}
if (!doc.id || !state[state.docsStateProp][doc.id]) {
return dispatch('insert', doc)
}
Expand All @@ -314,8 +335,9 @@ const actions = {
commit('INSERT_DOC', doc)
return dispatch('insertDoc', doc)
},
patch ({commit, dispatch, getters}, doc) {
if (!doc || !doc.id) return
patch ({commit, state, dispatch, getters}, doc) {
if (!doc) return
if (!doc.id && getters.collectionMode) return
commit('PATCH_DOC', doc)
return dispatch('patchDoc', {id: doc.id, fields: Object.keys(doc)})
},
Expand Down
12 changes: 8 additions & 4 deletions src/module/defaultConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ function syncHook (storeUpdateFn, store, id, doc, source, change) {
export default {
moduleNameSpace: 'firestore',
// this is the vuex module path that will be created
docsStateProp: 'docs',
docsStateProp: '',
// this is the state property where your docs will end up inside the module
// when not set your doc's props will be set directly to your vuex module's state
firestorePath: '',
// this is the firestore collection path to your documents. You can use `{userId}` which will be replaced with `Firebase.auth().uid`
mapType: 'collection',
firestoreRefType: 'collection',
// 'collection' only ('doc' not integrated yet)
userVuexPath: '',
// the path where your firebase user gets saved in vuex. Required to be able to have reactivity after login.
sync: {
type: '2way',
// '2way' only ('read only' not yet integrated)
where: [],
orderBy: [],
where: [], // only applicable on 'collection'
orderBy: [], // only applicable on 'collection'
defaultValues: {},
// About defaultValues:
// These are the default properties that will be set on each doc that's synced to the store or comes out of the store.
Expand All @@ -39,6 +42,7 @@ export default {
modified: syncHook,
removed: syncHook,
// see the syncHook function above to see what you can do
// for firestoreRefType: 'doc' only use 'modified' syncHook
},
fetch: {
docLimit: 50, // defaults to 50
Expand Down
10 changes: 9 additions & 1 deletion src/module/errorCheck.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@

export default function errorCheck (config) {
let reqProps = ['firestorePath']
let reqProps = ['firestorePath', 'userVuexPath']
reqProps.forEach(prop => {
console.error(`Missing ${prop} from your config!`)
return false
})
if (/(\.|\/)/.test(config.docsStateProp)) {
console.error(`docsStateProp must only include letters from [a-z]`)
return false
}
if (/\./.test(config.moduleNameSpace)) {
console.error(`moduleNameSpace must only include letters from [a-z] and forward slashes '/'`)
return false
}
return true
}
27 changes: 20 additions & 7 deletions src/module/getters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,50 @@ import checkFillables from '../utils/checkFillables'

const getters = {
signedIn: (state, getters, rootState, rootGetters) => {
return rootState.user.user !== null
// return Firebase.auth().currentUser !== null
const user = getDeepRef(rootState, state.userVuexPath)
return (user !== null)
},
dbRef: (state, getters, rootState, rootGetters) => {
if (!getters.signedIn) return false
const userId = Firebase.auth().currentUser.uid
const path = state.firestorePath.replace('{userId}', userId)
return Firebase.firestore().collection(path)
return (state.firestoreRefType.toLowerCase() === 'collection')
? Firebase.firestore().collection(path)
: Firebase.firestore().doc(path)
},
storeRef: (state, getters, rootState) => {
const path = `${state.moduleNameSpace}/${state.docsStateProp}`
const path = (state.docsStateProp)
? `${state.moduleNameSpace}/${state.docsStateProp}`
: state.moduleNameSpace
return getDeepRef(rootState, path)
},
collectionMode: (state, getters, rootState) => {
return (state.firestoreRefType.toLowerCase() === 'collection')
},
prepareForPatch: (state, getters, rootState, rootGetters) =>
(ids = [], fields = []) => {
// get relevant data from the storeRef
const collectionMode = getters.collectionMode
if (!collectionMode) ids.push('singleDoc')
// returns {object} -> {id: data}
return ids.reduce((carry, id) => {
// Accept an extra condition to check
let check = state.patch.checkCondition
const check = state.patch.checkCondition
if (check && !check(id, fields, getters.storeRef)) return carry

let patchData = {}
// Patch specific fields only
if (fields.length) {
fields.forEach(field => {
patchData[field] = getters.storeRef[id][field]
patchData[field] = (collectionMode)
? getters.storeRef[id][field]
: getters.storeRef[field]
})
// Patch the whole item
} else {
patchData = copyObj(getters.storeRef[id])
patchData = (collectionMode)
? copyObj(getters.storeRef[id])
: copyObj(getters.storeRef)
patchData = checkFillables(patchData, state.patch.fillables, state.patch.guard)
}
patchData.updated_at = Firebase.firestore.FieldValue.serverTimestamp()
Expand Down
11 changes: 8 additions & 3 deletions src/module/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// store
import iniState from './state'
import defaultConfig from './defaultConfig'
import initialState from './state'
import iniMutations from './mutations'
import iniActions from './actions'
import iniGetters from './getters'
Expand All @@ -24,11 +25,15 @@ export default function (userConfig) {
delete conf.mutations
delete conf.actions
delete conf.getters
const state = iniState(userState, conf)

const docContainer = {}
if (conf.docsStateProp) docContainer[conf.docsStateProp] = {}
const state = Object.assign({}, initialState, defaultConfig, userState, conf, docContainer)

return {
namespaced: true,
state,
mutations: iniMutations(userMutations, state),
mutations: iniMutations(userMutations, Object.assign({}, initialState, userState)),
actions: iniActions(userActions),
getters: iniGetters(userGetters)
}
Expand Down
56 changes: 35 additions & 21 deletions src/module/mutations.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import { defaultMutations } from 'vuex-easy-access'

export default function (userMutations = {}, state) {
const vuexEasyMutations = defaultMutations(state)
const mutations = {
resetSyncStack(state) {
state.syncStack = {
updates: {},
deletions: [],
inserts: [],
debounceTimer: null
const mutations = {
resetSyncStack (state) {
state.syncStack = {
updates: {},
deletions: [],
inserts: [],
debounceTimer: null
}
},
INSERT_DOC (state, doc) {
if (state.firestoreRefType.toLowerCase() === 'doc') return
this._vm.$set(state[state.docsStateProp], doc.id, doc)
},
PATCH_DOC (state, doc) {
if (state.firestoreRefType.toLowerCase() === 'doc') {
if (!state.docsStateProp) {
return Object.keys(doc).forEach(key => {
this._vm.$set(state, key, doc[key])
})
}
},
INSERT_DOC (state, doc) {
this._vm.$set(state[state.docsStateProp], doc.id, doc)
},
PATCH_DOC (state, doc) {
this._vm.$set(state[state.docsStateProp], doc.id, Object.assign(
state[state.docsStateProp][doc.id], doc
))
},
DELETE_DOC (state, id) {
this._vm.$delete(state[state.docsStateProp], id)
},
doc = Object.assign(state[state.docsStateProp], doc)
state[state.docsStateProp] = doc
return
}
doc = (state[state.docsStateProp][doc.id])
? Object.assign(state[state.docsStateProp][doc.id], doc)
: doc
this._vm.$set(state[state.docsStateProp], doc.id, doc)
},
DELETE_DOC (state, id) {
if (state.firestoreRefType.toLowerCase() === 'doc') return
this._vm.$delete(state[state.docsStateProp], id)
}
}

export default function (userMutations = {}, state) {
const vuexEasyMutations = defaultMutations(state)
return Object.assign({}, vuexEasyMutations, mutations, userMutations)
}
10 changes: 2 additions & 8 deletions src/module/state.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import defaultConfig from './defaultConfig'

const state = {
export default {
// user: null,
syncStack: {
updates: {},
deletions: [],
Expand All @@ -13,9 +13,3 @@ const state = {
doneFetching: false,
stopPatchingTimeout: null,
}

export default function (userState = {}, userConfig = {}) {
const docsStateProp = {}
docsStateProp[userConfig.docsStateProp] = {}
return Object.assign({}, state, defaultConfig, userState, userConfig, docsStateProp)
}

0 comments on commit eaf0a05

Please sign in to comment.