From eaf0a05d1bae3ebcf3b53f4b2bb821ac61142116 Mon Sep 17 00:00:00 2001 From: Luca Ban - Black Birdy Date: Thu, 28 Jun 2018 03:40:27 +0900 Subject: [PATCH] WIP doc integration --- README.md | 2 +- src/index.js | 14 ++++---- src/module/actions.js | 66 ++++++++++++++++++++++++------------- src/module/defaultConfig.js | 12 ++++--- src/module/errorCheck.js | 10 +++++- src/module/getters.js | 27 +++++++++++---- src/module/index.js | 11 +++++-- src/module/mutations.js | 56 +++++++++++++++++++------------ src/module/state.js | 10 ++---- 9 files changed, 135 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index aa5799d6..ab717015 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/src/index.js b/src/index.js index f07fbc95..1d3ec6f6 100644 --- a/src/index.js +++ b/src/index.js @@ -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' @@ -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) } } } diff --git a/src/module/actions.js b/src/module/actions.js index 9514d01c..833aac02 100644 --- a/src/module/actions.js +++ b/src/module/actions.js @@ -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) @@ -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 @@ -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) }) @@ -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' && @@ -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') @@ -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) } @@ -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)}) }, diff --git a/src/module/defaultConfig.js b/src/module/defaultConfig.js index fbcfeeab..63abe21d 100644 --- a/src/module/defaultConfig.js +++ b/src/module/defaultConfig.js @@ -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. @@ -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 diff --git a/src/module/errorCheck.js b/src/module/errorCheck.js index 44621223..04d4c3de 100644 --- a/src/module/errorCheck.js +++ b/src/module/errorCheck.js @@ -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 } diff --git a/src/module/getters.js b/src/module/getters.js index c5196b05..641cea18 100644 --- a/src/module/getters.js +++ b/src/module/getters.js @@ -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() diff --git a/src/module/index.js b/src/module/index.js index 1efa3ced..515a3efe 100644 --- a/src/module/index.js +++ b/src/module/index.js @@ -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' @@ -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) } diff --git a/src/module/mutations.js b/src/module/mutations.js index 17fa8281..2b7c30e5 100644 --- a/src/module/mutations.js +++ b/src/module/mutations.js @@ -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) } diff --git a/src/module/state.js b/src/module/state.js index c382f101..0435aa86 100644 --- a/src/module/state.js +++ b/src/module/state.js @@ -1,6 +1,6 @@ -import defaultConfig from './defaultConfig' -const state = { +export default { + // user: null, syncStack: { updates: {}, deletions: [], @@ -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) -}