From 4b69df4ffcd14c9af0647a0c0fb03c5fd5cfefc7 Mon Sep 17 00:00:00 2001 From: Luca Ban - Black Birdy Date: Fri, 14 Dec 2018 15:28:26 +0900 Subject: [PATCH 1/3] =?UTF-8?q?small=20doc=20improvements=20=F0=9F=90=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/config-example.md | 10 +++++----- docs/extra-features.md | 9 ++++----- docs/firestore-fields-and-functions.md | 2 +- docs/guide.md | 20 +++++++++++--------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/config-example.md b/docs/config-example.md index 97906f7d..389c5c65 100644 --- a/docs/config-example.md +++ b/docs/config-example.md @@ -29,16 +29,16 @@ const firestoreModule = { deleteBatchHook: function (updateStore, ids, store) { return updateStore(ids) }, }, - // When items on the server side are changed: + // When docs on the server side are changed: serverChange: { defaultValues: {}, // HOOKS for changes on SERVER: - addedHook: function (updateStore, doc, id, store, source, change) { return updateStore(doc) }, - modifiedHook: function (updateStore, doc, id, store, source, change) { return updateStore(doc) }, - removedHook: function (updateStore, doc, id, store, source, change) { return updateStore(doc) }, + addedHook: function (updateStore, doc, id, store) { return updateStore(doc) }, + modifiedHook: function (updateStore, doc, id, store) { return updateStore(doc) }, + removedHook: function (updateStore, doc, id, store) { return updateStore(doc) }, }, - // When items are fetched through `dispatch('module/fetch', filters)`. + // When docs are fetched through `dispatch('module/fetch', filters)`. fetch: { // The max amount of documents to be fetched. Defaults to 50. docLimit: 50, diff --git a/docs/extra-features.md b/docs/extra-features.md index 6726630f..64e9b655 100644 --- a/docs/extra-features.md +++ b/docs/extra-features.md @@ -156,15 +156,14 @@ Exactly the same as above, but for changes that have occured on the server. You - *id:* the doc id returned in `change.doc.id` (see firestore documentation for more info) - *doc:* the doc returned in `change.doc.data()` (see firestore documentation for more info) -- *source:* of the change. Can be either `'local'` or `'server'` ```js { // your other vuex-easy-fire config... serverChange: { - addedHook: function (updateStore, doc, id, store, source, change) { updateStore(doc) }, - modifiedHook: function (updateStore, doc, id, store, source, change) { updateStore(doc) }, - removedHook: function (updateStore, doc, id, store, source, change) { updateStore(doc) }, + addedHook: function (updateStore, doc, id, store) { updateStore(doc) }, + modifiedHook: function (updateStore, doc, id, store) { updateStore(doc) }, + removedHook: function (updateStore, doc, id, store) { updateStore(doc) }, } } ``` @@ -278,7 +277,7 @@ If you create a `defaultValues` object, then each document from the server will Automatically convert Firestore Timestamps into `new Date()` objects! Do this by setting `'%convertTimestamp%'` as the value of a `defaultValues` prop. (see example below). **Use case 2: Reactivity**
-With VueJS, if you need a prop on an item to be fully reactive with your vue templates, it needs to exist from the start. If some docs in your user's firestore doesn't have all props (because you added new functionality to your app at later dates), the *retrieved docs will have reactivity problems!* +With VueJS, if you need a prop on an object to be fully reactive with your vue templates, it needs to exist from the start. If some docs in your user's firestore doesn't have all props (because you added new functionality to your app at later dates), the *retrieved docs will have reactivity problems!* However, if you add these props to `defaultValues` with some value (or just `'null'`), vuex-easy-firestore will automatically add those props to the doc *before* inserting it into vuex! diff --git a/docs/firestore-fields-and-functions.md b/docs/firestore-fields-and-functions.md index 7500fbaa..c24bfff4 100644 --- a/docs/firestore-fields-and-functions.md +++ b/docs/firestore-fields-and-functions.md @@ -7,7 +7,7 @@ Just like Firestore, Vuex Easy Firestore supports the usage of *arrayUnion* and ```js import { arrayUnion, arrayRemove } from 'vuex-easy-firestore' -store.patch('myModule/patch', { +store.dispatch('myModule/patch', { id: '001', array1: arrayUnion('a new val'), array2: arrayRemove('some val'), diff --git a/docs/guide.md b/docs/guide.md index e039ad13..cf58cc8f 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -18,10 +18,12 @@ There are two ways to use vuex-easy-firestore, in 'collection' or 'doc' mode. Yo - Use when working with a single doc. - eg. the "settings" or "config" of a user. -Depending on which mode there are some small changes, but the syntax is mostly the same. +Whether a vuex module is set to 'doc' or 'collection' mode, will have small changes in the actions you can do, but the syntax is mostly the same. The sync is fully robust and **automatically groups any api calls per 1000 ms**. You don't have to worry about optimising/limiting the api calls, it's all done automatically! (Only one api call per 1000ms will be made for a maximum of 500 changes, if there are more changes queued it will automatically be split over 2 api calls). +> If you still are confused how to set up your database structure when it comes to documents vs collections, I highly recommend to check [this guide from Firebase](https://firebase.google.com/docs/firestore/manage-data/structure-data) itself. + ## 'collection' mode Opening [the DB channel](setup.html#open-db-channel) will retrieve all docs in your collection and add them to the vuex-module. @@ -41,33 +43,33 @@ dispatch('moduleName/delete', id) ```js const id = '123' -// Add the `id` as a prop to the item you want to set/update: +// Add the `id` as a prop to the document you want to set/update: dispatch('moduleName/set', {id, name: 'my new name'}) -// OR use the `id` as [key] and the item as its value: +// OR use the `id` as [key] and the document as its value: dispatch('moduleName/set', {[id]: {name: 'my new name'}}) // Please note that only the `name` will be updated, and other fields are left alone! ``` -There are two ways to delete things: the whole item **or just a sub-property!** +There are two ways to delete things: the whole document **or just a field!** (A field is a property of that document) ```js -// Delete the whole item: +// Delete the whole document: dispatch('moduleName/delete', id) -// Delete a sub-property of an item: +// Delete a field of a document: dispatch('moduleName/delete', `${id}.tags.water`) -// the items looks like: +// the document looks like: { id: '123', tags: { fire: true, - water: true, // only `water` will be deleted from the item! + water: true, // only `water` will be deleted in this example! } } ``` -In the above example you can see that you can delete a sub-property by passing a string and separate sub-props with `.` +In the above example you can see that you can delete a field (or property) by passing a string and separate sub-props with `.` (See [here](firestore-fields-and-functions.html#delete-fields) for more info on deleting fields) For batch actions see [here](#batch-updates-inserts-deletions). From 45b19550840d7ee68aca02104dd9a17d961dfc8c Mon Sep 17 00:00:00 2001 From: Luca Ban - Black Birdy Date: Sat, 15 Dec 2018 15:38:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Fix=20rare=20bug=20=F0=9F=A6=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/index.cjs.js | 2 -- dist/index.esm.js | 2 -- docs/ja/README.md | 58 +++++++++++++++++++++++++++++++++++++++ package.json | 2 +- src/module/actions.ts | 1 - test/helpers/index.cjs.js | 2 -- test/vuex-easy-access.js | 8 +++--- 7 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 docs/ja/README.md diff --git a/dist/index.cjs.js b/dist/index.cjs.js index 56e6ec9a..a1ec1413 100644 --- a/dist/index.cjs.js +++ b/dist/index.cjs.js @@ -707,8 +707,6 @@ function pluginActions (Firebase$$1) { return console.error('[vuex-easy-firestore] ids needs to be an array'); if (id) ids.push(id); - if (doc.id) - delete doc.id; // 1. Prepare for patching var syncStackItems = getters.prepareForPatch(ids, doc); // 2. Push to syncStack diff --git a/dist/index.esm.js b/dist/index.esm.js index 79cac95c..e3178671 100644 --- a/dist/index.esm.js +++ b/dist/index.esm.js @@ -702,8 +702,6 @@ function pluginActions (Firebase$$1) { return console.error('[vuex-easy-firestore] ids needs to be an array'); if (id) ids.push(id); - if (doc.id) - delete doc.id; // 1. Prepare for patching var syncStackItems = getters.prepareForPatch(ids, doc); // 2. Push to syncStack diff --git a/docs/ja/README.md b/docs/ja/README.md new file mode 100644 index 00000000..0e4965fd --- /dev/null +++ b/docs/ja/README.md @@ -0,0 +1,58 @@ +--- +home: true +# heroImage: /hero.png +actionText: 始めよう → +actionLink: /setup +features: +- title: 何よりシンプル + details: Minimal setup to get a vuex-module synced with Firestore automatically. +- title: パワフル + details: Easy to use features include filtering, hooks, automatic Firestore Timestamp conversion & much more. +- title: パフォーマンスが第一 + details: Automatic 2-way sync is fully optimised through api call batches. +footer: MIT Licensed | Copyright © 2018-present Luca Ban - Mesqueeb +--- + +# Overview + +たった4つの行のコードを追加するだけで、VuexモジュールがFirestoreと自動的に同期される状態にできる。 + +```js +const userModule = { + firestorePath: 'users/{userId}/data', + firestoreRefType: 'collection', // or 'doc' + moduleName: 'userData', + statePropName: 'docs', + // モジュールのその他 state, actions など +} +// vuex-easy-firestoreでこのuserModuleをvuex pluginとしてstoreに入れるだけ +``` + +and Alakazam! Now you have a vuex module called `userData` with `state: {docs: {}}`. +All firestore documents in your collection will be added with the doc's id as key inside `docs` in your state. + +Now you just update and add docs with `dispatch('userData/set', newItem)` and forget about the rest! + +# Features + +- Complete 2-way sync between your Vuex module & Firestore +- [Automatic Firestore Timestamp conversion](extra-features.html#defaultvalues-set-after-server-retrieval) +- [Fillables](extra-features.html#fillables-and-guard) (limit props able to sync) +- [Hooks](extra-features.html#hooks-before-insert-patch-delete) (before / after sync) +- [Where / orderBy filters](extra-features.html#filters) + +# Motivation + +I didn't like writing an entire an API wrapper from scratch for firestore every single project. If only a vuex module could be in perfect sync with firestore without having to code all the boilerplate yourself... + +And that's how Vuex Easy Firestore was born. + +
Installation and setup →
+ +# Support + +If you like what I built, you can say thanks by buying me a coffee! :) + +Buy me a coffeeBuy me a coffee + +Thank you so much!! Every little bit helps. diff --git a/package.json b/package.json index 934080e8..cb4d9e2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vuex-easy-firestore", - "version": "1.20.3", + "version": "1.20.4", "description": "Easy coupling of firestore and a vuex module. 2-way sync with 0 boilerplate!", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", diff --git a/src/module/actions.ts b/src/module/actions.ts index 476f8171..dfd9b6c6 100644 --- a/src/module/actions.ts +++ b/src/module/actions.ts @@ -41,7 +41,6 @@ export default function (Firebase: any): AnyObject { // 0. payload correction (only arrays) if (!isArray(ids)) return console.error('[vuex-easy-firestore] ids needs to be an array') if (id) ids.push(id) - if (doc.id) delete doc.id // 1. Prepare for patching const syncStackItems = getters.prepareForPatch(ids, doc) diff --git a/test/helpers/index.cjs.js b/test/helpers/index.cjs.js index 1d37296e..a55ec54e 100644 --- a/test/helpers/index.cjs.js +++ b/test/helpers/index.cjs.js @@ -793,8 +793,6 @@ function pluginActions (Firebase$$1) { return console.error('[vuex-easy-firestore] ids needs to be an array'); if (id) ids.push(id); - if (doc.id) - delete doc.id; // 1. Prepare for patching var syncStackItems = getters.prepareForPatch(ids, doc); // 2. Push to syncStack diff --git a/test/vuex-easy-access.js b/test/vuex-easy-access.js index 0850842b..92f9748e 100644 --- a/test/vuex-easy-access.js +++ b/test/vuex-easy-access.js @@ -10,7 +10,7 @@ const boxRef = store.getters['pokemonBox/dbRef'] const charRef = store.getters['mainCharacter/dbRef'] test('[COLLECTION] set & delete: top lvl', async t => { - await wait(2) + await wait(10) const id = boxRef.doc().id const id2 = boxRef.doc().id const date = new Date() @@ -84,7 +84,7 @@ test('[COLLECTION] set & delete: top lvl', async t => { }) test('[COLLECTION] set & delete: deep', async t => { - await wait(2) + await wait(10) let docR, doc const id = boxRef.doc().id @@ -147,7 +147,7 @@ test('[COLLECTION] set & delete: deep', async t => { // }) test('[DOC] set & delete: top lvl', async t => { - await wait(2) + await wait(10) // EXISTING prop set await store.set('mainCharacter/items', ['Pokeball']) t.true(char.items.includes('Pokeball')) @@ -174,7 +174,7 @@ test('[DOC] set & delete: top lvl', async t => { }) test('[DOC] set & delete: deep', async t => { - await wait(2) + await wait(10) await store.set('mainCharacter', {a: {met: {de: 'aba'}}}) t.truthy(char.a.met.de) t.is(char.a.met.de, 'aba') From 833b41d37d7b744530bb62f52dfe1536a4c64a18 Mon Sep 17 00:00:00 2001 From: Luca Ban - Black Birdy Date: Sat, 15 Dec 2018 23:15:47 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Sub-props=20for=20fillables=20and=20guard?= =?UTF-8?q?=20=F0=9F=90=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/index.cjs.js | 55 ++++++--- dist/index.esm.js | 55 ++++++--- package.json | 2 +- src/utils/checkFillables.ts | 56 +++++++-- test/helpers/index.cjs.js | 114 +++++++++++++++--- test/helpers/store/store.ts | 12 +- test/helpers/store/testNestedFillables.ts | 28 +++++ test/helpers/store/testNestedGuard.ts | 25 ++++ test/syncFillablesGuard.js | 76 ++++++++++++ test/{syncConfig.js => syncHooks.js} | 26 ---- test/utils/checkFillables.js | 62 +++++++++- .../helpers/store/testNestedFillables.d.ts | 21 ++++ types/test/helpers/store/testNestedGuard.d.ts | 18 +++ 13 files changed, 465 insertions(+), 85 deletions(-) create mode 100644 test/helpers/store/testNestedFillables.ts create mode 100644 test/helpers/store/testNestedGuard.ts create mode 100644 test/syncFillablesGuard.js rename test/{syncConfig.js => syncHooks.js} (63%) create mode 100644 types/test/helpers/store/testNestedFillables.d.ts create mode 100644 types/test/helpers/store/testNestedGuard.d.ts diff --git a/dist/index.cjs.js b/dist/index.cjs.js index a1ec1413..c5697767 100644 --- a/dist/index.cjs.js +++ b/dist/index.cjs.js @@ -1241,6 +1241,46 @@ function flattenToPaths (object) { return retrievePaths(object, null, result); } +function recursiveCheck(obj, fillables, guard, pathUntilNow) { + if (pathUntilNow === void 0) { pathUntilNow = ''; } + if (!isWhat.isPlainObject(obj)) { + console.log('obj → ', obj); + return obj; + } + return Object.keys(obj).reduce(function (carry, key) { + var path = pathUntilNow; + if (path) + path += '.'; + path += key; + // check guard regardless + if (guard.includes(path)) { + return carry; + } + var value = obj[key]; + // check fillables up to this point + if (fillables.length) { + var passed_1 = false; + fillables.forEach(function (fillable) { + var pathDepth = path.split('.').length; + var fillableDepth = fillable.split('.').length; + var fillableUpToNow = fillable.split('.').slice(0, pathDepth).join('.'); + var pathUpToFillableDepth = path.split('.').slice(0, fillableDepth).join('.'); + if (fillableUpToNow === pathUpToFillableDepth) + passed_1 = true; + }); + // there's not one fillable that allows up to now + if (!passed_1) + return carry; + } + // no fillables or fillables up to now allow it + if (!isWhat.isPlainObject(value)) { + carry[key] = value; + return carry; + } + carry[key] = recursiveCheck(obj[key], fillables, guard, path); + return carry; + }, {}); +} /** * Checks all props of an object and deletes guarded and non-fillables. * @@ -1253,20 +1293,7 @@ function flattenToPaths (object) { function checkFillables (obj, fillables, guard) { if (fillables === void 0) { fillables = []; } if (guard === void 0) { guard = []; } - if (!isWhat.isPlainObject(obj)) - return obj; - return Object.keys(obj).reduce(function (carry, key) { - // check fillables - if (fillables.length && !fillables.includes(key)) { - return carry; - } - // check guard - if (guard.includes(key)) { - return carry; - } - carry[key] = obj[key]; - return carry; - }, {}); + return recursiveCheck(obj, fillables, guard); } /** diff --git a/dist/index.esm.js b/dist/index.esm.js index e3178671..4567b5fa 100644 --- a/dist/index.esm.js +++ b/dist/index.esm.js @@ -1236,6 +1236,46 @@ function flattenToPaths (object) { return retrievePaths(object, null, result); } +function recursiveCheck(obj, fillables, guard, pathUntilNow) { + if (pathUntilNow === void 0) { pathUntilNow = ''; } + if (!isPlainObject(obj)) { + console.log('obj → ', obj); + return obj; + } + return Object.keys(obj).reduce(function (carry, key) { + var path = pathUntilNow; + if (path) + path += '.'; + path += key; + // check guard regardless + if (guard.includes(path)) { + return carry; + } + var value = obj[key]; + // check fillables up to this point + if (fillables.length) { + var passed_1 = false; + fillables.forEach(function (fillable) { + var pathDepth = path.split('.').length; + var fillableDepth = fillable.split('.').length; + var fillableUpToNow = fillable.split('.').slice(0, pathDepth).join('.'); + var pathUpToFillableDepth = path.split('.').slice(0, fillableDepth).join('.'); + if (fillableUpToNow === pathUpToFillableDepth) + passed_1 = true; + }); + // there's not one fillable that allows up to now + if (!passed_1) + return carry; + } + // no fillables or fillables up to now allow it + if (!isPlainObject(value)) { + carry[key] = value; + return carry; + } + carry[key] = recursiveCheck(obj[key], fillables, guard, path); + return carry; + }, {}); +} /** * Checks all props of an object and deletes guarded and non-fillables. * @@ -1248,20 +1288,7 @@ function flattenToPaths (object) { function checkFillables (obj, fillables, guard) { if (fillables === void 0) { fillables = []; } if (guard === void 0) { guard = []; } - if (!isPlainObject(obj)) - return obj; - return Object.keys(obj).reduce(function (carry, key) { - // check fillables - if (fillables.length && !fillables.includes(key)) { - return carry; - } - // check guard - if (guard.includes(key)) { - return carry; - } - carry[key] = obj[key]; - return carry; - }, {}); + return recursiveCheck(obj, fillables, guard); } /** diff --git a/package.json b/package.json index cb4d9e2f..388ab0a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vuex-easy-firestore", - "version": "1.20.4", + "version": "1.21.0", "description": "Easy coupling of firestore and a vuex module. 2-way sync with 0 boilerplate!", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", diff --git a/src/utils/checkFillables.ts b/src/utils/checkFillables.ts index f4d19654..fe212513 100644 --- a/src/utils/checkFillables.ts +++ b/src/utils/checkFillables.ts @@ -1,6 +1,48 @@ import { isPlainObject } from 'is-what' import { AnyObject } from '../declarations' +function recursiveCheck ( + obj: object, + fillables: string[], + guard: string[], + pathUntilNow: string = '' +): AnyObject { + if (!isPlainObject(obj)) { + console.log('obj → ', obj) + return obj + } + return Object.keys(obj).reduce((carry, key) => { + let path = pathUntilNow + if (path) path += '.' + path += key + // check guard regardless + if (guard.includes(path)) { + return carry + } + const value = obj[key] + // check fillables up to this point + if (fillables.length) { + let passed = false + fillables.forEach(fillable => { + const pathDepth = path.split('.').length + const fillableDepth = fillable.split('.').length + const fillableUpToNow = fillable.split('.').slice(0, pathDepth).join('.') + const pathUpToFillableDepth = path.split('.').slice(0, fillableDepth).join('.') + if (fillableUpToNow === pathUpToFillableDepth) passed = true + }) + // there's not one fillable that allows up to now + if (!passed) return carry + } + // no fillables or fillables up to now allow it + if (!isPlainObject(value)) { + carry[key] = value + return carry + } + carry[key] = recursiveCheck(obj[key], fillables, guard, path) + return carry + }, {}) +} + /** * Checks all props of an object and deletes guarded and non-fillables. * @@ -15,17 +57,5 @@ export default function ( fillables: string[] = [], guard: string[] = [] ): AnyObject { - if (!isPlainObject(obj)) return obj - return Object.keys(obj).reduce((carry, key) => { - // check fillables - if (fillables.length && !fillables.includes(key)) { - return carry - } - // check guard - if (guard.includes(key)) { - return carry - } - carry[key] = obj[key] - return carry - }, {}) + return recursiveCheck(obj, fillables, guard) } diff --git a/test/helpers/index.cjs.js b/test/helpers/index.cjs.js index a55ec54e..7f005e10 100644 --- a/test/helpers/index.cjs.js +++ b/test/helpers/index.cjs.js @@ -135,6 +135,55 @@ var mainCharacter = { getters: {}, }; +function initialState$5() { + return { + nested: { + fillables: { + yes: 0, + no: 0, + }, + } + }; +} +var testNestedFillables = { + // easy firestore config + firestorePath: 'configTests/nestedFillables', + firestoreRefType: 'doc', + moduleName: 'nestedFillables', + statePropName: '', + sync: { + fillables: ['nested.fillables.yes'], + }, + // module + state: initialState$5(), + mutations: createEasyAccess.defaultMutations(initialState$5()), + actions: {}, + getters: {}, +}; + +function initialState$6() { + return { + nested: { + guard: true, + } + }; +} +var testNestedGuard = { + // easy firestore config + firestorePath: 'configTests/nestedGuard', + firestoreRefType: 'doc', + moduleName: 'nestedGuard', + statePropName: '', + sync: { + guard: ['nested.guard'], + }, + // module + state: initialState$6(), + mutations: createEasyAccess.defaultMutations(initialState$6()), + actions: {}, + getters: {}, +}; + require('@firebase/firestore'); /** @@ -1327,6 +1376,46 @@ function flattenToPaths (object) { return retrievePaths(object, null, result); } +function recursiveCheck(obj, fillables, guard, pathUntilNow) { + if (pathUntilNow === void 0) { pathUntilNow = ''; } + if (!isWhat.isPlainObject(obj)) { + console.log('obj → ', obj); + return obj; + } + return Object.keys(obj).reduce(function (carry, key) { + var path = pathUntilNow; + if (path) + path += '.'; + path += key; + // check guard regardless + if (guard.includes(path)) { + return carry; + } + var value = obj[key]; + // check fillables up to this point + if (fillables.length) { + var passed_1 = false; + fillables.forEach(function (fillable) { + var pathDepth = path.split('.').length; + var fillableDepth = fillable.split('.').length; + var fillableUpToNow = fillable.split('.').slice(0, pathDepth).join('.'); + var pathUpToFillableDepth = path.split('.').slice(0, fillableDepth).join('.'); + if (fillableUpToNow === pathUpToFillableDepth) + passed_1 = true; + }); + // there's not one fillable that allows up to now + if (!passed_1) + return carry; + } + // no fillables or fillables up to now allow it + if (!isWhat.isPlainObject(value)) { + carry[key] = value; + return carry; + } + carry[key] = recursiveCheck(obj[key], fillables, guard, path); + return carry; + }, {}); +} /** * Checks all props of an object and deletes guarded and non-fillables. * @@ -1339,20 +1428,7 @@ function flattenToPaths (object) { function checkFillables (obj, fillables, guard) { if (fillables === void 0) { fillables = []; } if (guard === void 0) { guard = []; } - if (!isWhat.isPlainObject(obj)) - return obj; - return Object.keys(obj).reduce(function (carry, key) { - // check fillables - if (fillables.length && !fillables.includes(key)) { - return carry; - } - // check guard - if (guard.includes(key)) { - return carry; - } - carry[key] = obj[key]; - return carry; - }, {}); + return recursiveCheck(obj, fillables, guard); } /** @@ -1697,7 +1773,15 @@ var settings = { timestampsInSnapshots: true }; firestore.settings(settings); var easyAccess = createEasyAccess__default({ vuexEasyFirestore: true }); -var easyFirestores = vuexEasyFirestore([pokemonBox, mainCharacter, testPathVar, testMutations1, testMutations2], { logging: false, FirebaseDependency: Firebase }); +var easyFirestores = vuexEasyFirestore([ + pokemonBox, + mainCharacter, + testPathVar, + testMutations1, + testMutations2, + testNestedFillables, + testNestedGuard, +], { logging: false, FirebaseDependency: Firebase }); var storeObj = { plugins: [easyFirestores, easyAccess] }; diff --git a/test/helpers/store/store.ts b/test/helpers/store/store.ts index e34bf770..105d70fb 100644 --- a/test/helpers/store/store.ts +++ b/test/helpers/store/store.ts @@ -3,6 +3,8 @@ import testPathVar from './testPathVar' import testMutations1 from './testMutationsNoStateProp' import testMutations2 from './testMutationsWithStateProp' import mainCharacter from './mainCharacter' +import testNestedFillables from './testNestedFillables' +import testNestedGuard from './testNestedGuard' import createFirestores from '../../../src/index' import createEasyAccess from 'vuex-easy-access' // import Firebase from '../firestoreMock' @@ -10,7 +12,15 @@ import Firebase from '../firestore' const easyAccess = createEasyAccess({vuexEasyFirestore: true}) const easyFirestores = createFirestores( - [pokemonBox, mainCharacter, testPathVar, testMutations1, testMutations2], + [ + pokemonBox, + mainCharacter, + testPathVar, + testMutations1, + testMutations2, + testNestedFillables, + testNestedGuard, + ], {logging: false, FirebaseDependency: Firebase} ) diff --git a/test/helpers/store/testNestedFillables.ts b/test/helpers/store/testNestedFillables.ts new file mode 100644 index 00000000..ba8022ca --- /dev/null +++ b/test/helpers/store/testNestedFillables.ts @@ -0,0 +1,28 @@ +import { defaultMutations } from 'vuex-easy-access' + +function initialState () { + return { + nested: { + fillables: { + yes: 0, + no: 0, + }, + } + } +} + +export default { + // easy firestore config + firestorePath: 'configTests/nestedFillables', + firestoreRefType: 'doc', + moduleName: 'nestedFillables', + statePropName: '', + sync: { + fillables: ['nested.fillables.yes'], + }, + // module + state: initialState(), + mutations: defaultMutations(initialState()), + actions: {}, + getters: {}, +} diff --git a/test/helpers/store/testNestedGuard.ts b/test/helpers/store/testNestedGuard.ts new file mode 100644 index 00000000..40d14c2a --- /dev/null +++ b/test/helpers/store/testNestedGuard.ts @@ -0,0 +1,25 @@ +import { defaultMutations } from 'vuex-easy-access' + +function initialState () { + return { + nested: { + guard: true, + } + } +} + +export default { + // easy firestore config + firestorePath: 'configTests/nestedGuard', + firestoreRefType: 'doc', + moduleName: 'nestedGuard', + statePropName: '', + sync: { + guard: ['nested.guard'], + }, + // module + state: initialState(), + mutations: defaultMutations(initialState()), + actions: {}, + getters: {}, +} diff --git a/test/syncFillablesGuard.js b/test/syncFillablesGuard.js new file mode 100644 index 00000000..356ac34b --- /dev/null +++ b/test/syncFillablesGuard.js @@ -0,0 +1,76 @@ +import test from 'ava' +import wait from './helpers/wait' +import {storeSyncConfig as store} from './helpers/index.cjs.js' + +const box = store.state.pokemonBox +const boxRef = store.getters['pokemonBox/dbRef'] +// const char = store.state.mainCharacter +// const charRef = store.getters['mainCharacter/dbRef'] + +test('[COLLECTION] sync: fillables & guard', async t => { + const id = boxRef.doc().id + store.dispatch('pokemonBox/insert', {name: 'Squirtle', id, type: ['water'], fillable: true, guarded: true, unmentionedProp: true}) + .catch(console.error) + t.truthy(box.pokemon[id]) + t.is(box.pokemon[id].name, 'Squirtle') + t.is(box.pokemon[id].fillable, true) + t.is(box.pokemon[id].guarded, true) // no prop filtering on local changes + t.is(box.pokemon[id].unmentionedProp, true) // no prop filtering on local changes + // fetch from server to check if guarded is undefined or not + await wait(2) + const docR = await boxRef.doc(id).get() + const doc = docR.data() + t.truthy(doc) + t.is(doc.name, 'Squirtle') + t.is(doc.fillable, true) + t.falsy(doc.guarded) + t.falsy(doc.unmentionedProp) + t.is(doc.guarded, undefined) + t.is(doc.unmentionedProp, undefined) +}) + +test('[DOC] sync: fillables & guard', async t => { + t.pass() +}) + +const fil = store.state.nestedFillables +const filRef = store.getters['nestedFillables/dbRef'] + +test('[DOC] sync: nested fillables', async t => { + store.dispatch('nestedFillables/set', { + nested: {fillables: {yes: 1, no: 2}}, + newProp: 3, + }).catch(console.error) + t.is(fil.nested.fillables.yes, 1) + t.is(fil.nested.fillables.no, 2) + t.is(fil.newProp, 3) + // fetch from server to check if guarded is undefined or not + await wait(2) + const docR = await filRef.get() + const doc = docR.data() + t.truthy(doc) + t.is(doc.nested.fillables.yes, 1) + t.is(doc.nested.fillables.no, undefined) + t.is(doc.newProp, undefined) +}) + +const gar = store.state.nestedGuard +const garRef = store.getters['nestedGuard/dbRef'] + +test('[DOC] sync: nested guard', async t => { + store.dispatch('nestedGuard/set', { + nested: {guard: 1, unguarded: 2}, + unguarded: 3, + }).catch(console.error) + t.is(gar.nested.guard, 1) + t.is(gar.nested.unguarded, 2) + t.is(gar.unguarded, 3) + // fetch from server to check if guarded is undefined or not + await wait(2) + const docR = await garRef.get() + const doc = docR.data() + t.truthy(doc) + t.is(doc.nested.guard, undefined) + t.is(doc.nested.unguarded, 2) + t.is(doc.unguarded, 3) +}) diff --git a/test/syncConfig.js b/test/syncHooks.js similarity index 63% rename from test/syncConfig.js rename to test/syncHooks.js index 454598fc..85697358 100644 --- a/test/syncConfig.js +++ b/test/syncHooks.js @@ -7,32 +7,6 @@ const boxRef = store.getters['pokemonBox/dbRef'] // const char = store.state.mainCharacter // const charRef = store.getters['mainCharacter/dbRef'] -test('[COLLECTION] sync: fillables & guard', async t => { - const id = boxRef.doc().id - store.dispatch('pokemonBox/insert', {name: 'Squirtle', id, type: ['water'], fillable: true, guarded: true, unmentionedProp: true}) - .catch(console.error) - t.truthy(box.pokemon[id]) - t.is(box.pokemon[id].name, 'Squirtle') - t.is(box.pokemon[id].fillable, true) - t.is(box.pokemon[id].guarded, true) // no prop filtering on local changes - t.is(box.pokemon[id].unmentionedProp, true) // no prop filtering on local changes - // fetch from server to check if guarded is undefined or not - await wait(2) - const docR = await boxRef.doc(id).get() - const doc = docR.data() - t.truthy(doc) - t.is(doc.name, 'Squirtle') - t.is(doc.fillable, true) - t.falsy(doc.guarded) - t.falsy(doc.unmentionedProp) - t.is(doc.guarded, undefined) - t.is(doc.unmentionedProp, undefined) -}) - -test('[DOC] sync: fillables & guard', async t => { - t.pass() -}) - test('[COLLECTION] sync: insertHook & patchHook', async t => { let doc, docR const id = boxRef.doc().id diff --git a/test/utils/checkFillables.js b/test/utils/checkFillables.js index bccaeb84..d8b2c188 100644 --- a/test/utils/checkFillables.js +++ b/test/utils/checkFillables.js @@ -1,16 +1,76 @@ import checkFillables from '../../src/utils/checkFillables' import test from 'ava' -test('checkFillables', t => { +test('check fillables FLAT', t => { let res, doc, fillables, guard doc = {name: 'n1', id: '1', filled: true, notfilled: false} fillables = ['name', 'filled', 'id'] res = checkFillables(doc, fillables, guard) t.deepEqual(res, {name: 'n1', id: '1', filled: true}) +}) +test('check guard FLAT', t => { + let res, doc, fillables, guard doc = {name: 'n1', id: '1', filled: true, guarded: true} fillables = [] guard = ['guarded'] res = checkFillables(doc, fillables, guard) t.deepEqual(res, {name: 'n1', id: '1', filled: true}) }) + +test('check fillables NESTED - single fillable', t => { + let res, doc, fillables, guard + doc = {nested: {fillables: {yes: 0, no: 0}}, secondProp: true} + fillables = ['nested.fillables.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0}}}) + fillables = ['nested.fillables'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}}) + fillables = ['nested'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}}) +}) + +test('check fillables NESTED - multiple fillable', t => { + let res, doc, fillables, guard + doc = {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true, no: false}} + fillables = ['nested.fillables.yes', 'secondProp.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0}}, secondProp: {yes: true}}) + fillables = ['nested.fillables', 'secondProp.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true}}) + fillables = ['nested', 'secondProp.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true}}) +}) + +test('check guard NESTED', t => { + let res, doc, fillables, guard + doc = {nested: {guard: {yes: 0, no: 0}}, secondProp: true} + guard = ['nested.guard.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {guard: {no: 0}}, secondProp: true}) + guard = ['nested.guard'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {}, secondProp: true}) + guard = ['nested'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {secondProp: true}) +}) + +test('check fillables NESTED - multiple fillable & guard', t => { + let res, doc, fillables, guard + doc = {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true, no: false}, guardedTop: true, guardedDeep: {yes: true, no: true}} + fillables = ['nested.fillables.yes', 'secondProp.yes', 'guardedTop', 'guardedDeep'] + guard = ['guardedTop', 'guardedDeep.yes'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0}}, secondProp: {yes: true}, guardedDeep: {no: true}}) + fillables = ['nested.fillables', 'secondProp.yes', 'guardedTop', 'guardedDeep'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true}, guardedDeep: {no: true}}) + fillables = ['nested', 'secondProp.yes', 'guardedTop', 'guardedDeep'] + res = checkFillables(doc, fillables, guard) + t.deepEqual(res, {nested: {fillables: {yes: 0, no: 0}}, secondProp: {yes: true}, guardedDeep: {no: true}}) +}) diff --git a/types/test/helpers/store/testNestedFillables.d.ts b/types/test/helpers/store/testNestedFillables.d.ts new file mode 100644 index 00000000..89bd4ae1 --- /dev/null +++ b/types/test/helpers/store/testNestedFillables.d.ts @@ -0,0 +1,21 @@ +declare const _default: { + firestorePath: string; + firestoreRefType: string; + moduleName: string; + statePropName: string; + sync: { + fillables: string[]; + }; + state: { + nested: { + fillables: { + yes: number; + no: number; + }; + }; + }; + mutations: import("vuex-easy-access/types/declarations").AnyObject; + actions: {}; + getters: {}; +}; +export default _default; diff --git a/types/test/helpers/store/testNestedGuard.d.ts b/types/test/helpers/store/testNestedGuard.d.ts new file mode 100644 index 00000000..bfb64dc9 --- /dev/null +++ b/types/test/helpers/store/testNestedGuard.d.ts @@ -0,0 +1,18 @@ +declare const _default: { + firestorePath: string; + firestoreRefType: string; + moduleName: string; + statePropName: string; + sync: { + guard: string[]; + }; + state: { + nested: { + guard: boolean; + }; + }; + mutations: import("vuex-easy-access/types/declarations").AnyObject; + actions: {}; + getters: {}; +}; +export default _default;