├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src └── index.js ├── tests ├── ExistingTodosDataFunction.vue ├── TodosDataFunctionWithSelector.vue ├── emptyDataFunction.vue ├── emptyDataObject.vue ├── global-mocks.js ├── noDataFunctionOrObject.vue └── testData.spec.js └── types └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig http://EditorConfig.org 2 | 3 | # Root EditorConfig file 4 | root = true 5 | 6 | # Universal 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.txt] 16 | charset = utf-8 17 | 18 | # JSON 19 | [*.json] 20 | end_of_line = lf 21 | indent_style = space 22 | indent_size = 4 23 | 24 | # PHP - PSR-2 standards 25 | [**.php] 26 | indent_style = space 27 | indent_size = 4 28 | 29 | # HTML 30 | [**.html] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | # SVG 35 | [**.svg] 36 | indent_style = space 37 | indent_size = 4 38 | 39 | # JavaScript 40 | [**.js] 41 | indent_style = space 42 | indent_size = 4 43 | 44 | # CSS 45 | [**.css] 46 | indent_style = space 47 | indent_size = 4 48 | 49 | # SASS 50 | [**.scss] 51 | indent_style = space 52 | indent_size = 4 53 | 54 | # YAML 55 | [**.yml] 56 | indent_style = space 57 | indent_size = 2 58 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | /* Highlighting 2 | 0 = OFF 3 | 1 = WARNING 4 | 2 = ERROR 5 | */ 6 | 7 | { 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "indent": [ 11 | 2, 12 | 4, 13 | { "SwitchCase": 1 } 14 | ], 15 | "quotes": [ 16 | 2, 17 | "single", 18 | "avoid-escape" 19 | ], 20 | "brace-style": [2, "1tbs"], 21 | "comma-dangle": [2, "always-multiline"], 22 | "consistent-return": 2, 23 | "linebreak-style": [ 24 | 2, 25 | "unix" 26 | ], 27 | "semi": [ 28 | 2, 29 | "always" 30 | ], 31 | "no-console": 0, 32 | "no-undef": 0, 33 | "no-shadow": 0, 34 | "no-bitwise": 2, 35 | "eol-last": 2, 36 | "dot-notation": 2, 37 | "dot-location": [2, "property"], 38 | "eqeqeq": [2, "allow-null"], 39 | "no-inner-declarations": [2, "functions"], 40 | "no-multi-spaces": 2, 41 | "no-unused-expressions": 2, 42 | "no-unused-vars": 0, 43 | "keyword-spacing": 2, 44 | "space-before-blocks": 2, 45 | "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}], 46 | "strict": [2, "global"] 47 | }, 48 | "env": { 49 | "browser": true 50 | }, 51 | "parserOptions": { 52 | "sourceType": "module", 53 | "ecmaVersion": 2017 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.css text eol=lf 11 | *.html text eol=lf 12 | *.js text eol=lf 13 | *.json text eol=lf 14 | *.md text eol=lf 15 | *.sh text eol=lf 16 | *.txt text eol=lf 17 | *.xml text eol=lf 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['https://www.paypal.me/mdslktr'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .idea 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 4, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Simon Kunz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pouch Vue 2 | 3 | ## This Plugin is not under active development anymore since none of the maintaining members are actively using it. 4 | 5 | ##### Basic structure copied from https://github.com/buhrmi/vue-pouch with a lot of api changes though. TypeScript support included too. 6 | 7 | ## Installation 8 | Make sure to have `pouchdb-browser` (or `pouchdb` depending on what you need) `pouchdb-find` and `pouchdb-live-find` installed 9 | ````sh 10 | npm i pouchdb-browser pouchdb-live-find pouchdb-find 11 | ```` 12 | 13 | Install via npm: 14 | ```sh 15 | npm install --save pouch-vue 16 | ``` 17 | 18 | The only requirement is that `pouchdb-live-find` is installed: 19 | ```javascript 20 | import PouchDB from 'pouchdb-browser' 21 | import PouchFind from 'pouchdb-find' 22 | import PouchLiveFind from 'pouchdb-live-find' 23 | 24 | PouchDB.plugin(PouchFind) 25 | PouchDB.plugin(PouchLiveFind) 26 | ``` 27 | 28 | If you want to use remote databases (CouchDB, Cloudant, etc.), you should also install the authentication plugin: 29 | ```javascript 30 | PouchDB.plugin(require('pouchdb-authentication')); 31 | ``` 32 | Then, plug VuePouch into Vue: 33 | ```javascript 34 | import Vue from 'vue'; 35 | import PouchVue from 'pouch-vue'; 36 | 37 | Vue.use(PouchVue, { 38 | pouch: PouchDB, // optional if `PouchDB` is available on the global object 39 | defaultDB: 'remoteDbName', // this is used as a default connect/disconnect database 40 | optionDB: {}, // this is used to include a custom fetch() method (see TypeScript example) 41 | debug: '*' // optional - See `https://pouchdb.com/api.html#debug_mode` for valid settings (will be a separate Plugin in PouchDB 7.0) 42 | }); 43 | ``` 44 | ### Known issue with PouchDB v7.0 45 | 46 | PouchDB v7.0 introduced [an issue with fetch using different defaults than XHR for cross-domain requests](https://github.com/pouchdb/pouchdb/issues/7391). The issue was fixed in PouchDB v7.1.1 so that fetch defaults now include 'credentials' just as XHR defaults come with credentials. If you are using PouchDB v7.0 you will get a 401 Unauthorized error. The workaround for PouchDB v7.0 is to override the fetch function in the defaults: 47 | 48 | ```javascript 49 | Vue.use(pouchVue,{ 50 | pouch: PouchDB, 51 | defaultDB: 'todos', 52 | optionsDB: { 53 | fetch: function (url:any, opts:any) { 54 | opts.credentials = 'include'; 55 | return PouchDB.fetch(url, opts); 56 | } 57 | } 58 | }) 59 | ``` 60 | ## API 61 | ### $pouch 62 | 63 | `$pouch` is made available on all vue instances and implements most of pouchdbs current API (https://pouchdb.com/api.html). 64 | Default events are mounted on each db you connect to: https://pouchdb.com/api.html#events. When a database is created `pouchdb-db-created` is emitted and `pouchdb-db-destroyed` when it's destroyed (which you can listen to with `this.$on(EVENT_NAME)`). 65 | 66 | #### Methods 67 | All Methods return a promise and mirror or extend the API from pouchdb. 68 | 69 | * `$pouch.getSession(OPTIONAL db)`: Returns the current session if already logged in to the defaultDB or given remote DB. 70 | * `$pouch.connect(username, password, OPTIONAL db)`: Connects you to the defaultDB or given remote DB and returns the user object on success. 71 | * `$pouch.disconnect(OPTIONAL db)`: Disconnects you from the defaultDB or given remote DB and clears the session data. 72 | * `$pouch.createUser(name, password, OPTIONAL db)`: Creates a user in the defaultDB or given remote DB and starts a new session. 73 | * `$pouch.putUser(name, OPTIONAL metadata, OPTIONAL db)`: Update a user in the defaultDB or given remote DB and returns the user object on success. [pouchdb-authentication API : putUser](https://github.com/pouchdb-community/pouchdb-authentication/blob/master/docs/api.md#dbputuserusername-opts--callback) 74 | * `$pouch.deleteUser(name, OPTIONAL db)`: Delete a user in the defaultDB or given remote DB and returns response. [pouchdb-authentication API : deleteUser](https://github.com/pouchdb-community/pouchdb-authentication/blob/master/docs/api.md#dbdeleteuserusername-opts--callback) 75 | * `$pouch.signUpAdmin(adminUsername, adminPassword, OPTIONAL db)`: Sign up a new admin and returns response. [pouchdb-authentication API : signUpAdmin](https://github.com/pouchdb-community/pouchdb-authentication/blob/master/docs/api.md#dbsignupadminusername-password--options--callback) 76 | * `$pouch.deleteAdmin(name, OPTIONAL db)`:Delete an admin and returns response. [pouchdb-authentication API : deleteAdmin](https://github.com/pouchdb-community/pouchdb-authentication/blob/master/docs/api.md#dbdeleteadminusername-opts--callback) 77 | ___ 78 | * `$pouch.destroy(OPTIONAL db)`: same as https://pouchdb.com/api.html#delete_database 79 | * `$pouch.defaults(options)`: same as https://pouchdb.com/api.html#defaults 80 | ___ 81 | * `$pouch.sync(localDatabase, OPTIONAL remoteDatabase, OPTIONAL options)`: The optional remoteDatabase parameter will use the default db set in the pouch options initially. Basically the same as PouchDB.sync(local, remote, {live: true, retry: true}). Also, if the browser has an active session cookie, it will fetch session data (username, etc) from the remote server. **BONUS:** If your remote database runs CouchDB 2.0 or higher, you can also specify a Mango Selector that is used to filter documents coming from the remote server. Callback functions will be invoked with the name `pouchdb-[method]-[type]`. So in this case you can use `this.$on('pouchdb-sync-change', callback(data))` to listen when a change occurs. See https://pouchdb.com/api.html#sync for a full list of events you can use. 82 | 83 | **default options (will be merged with the options passed in)**: 84 | ```javascript 85 | { 86 | live: true, 87 | retry: true, 88 | back_off_function: (delay) => { 89 | if (delay === 0) { 90 | return 1000; 91 | } 92 | return delay * 3; 93 | }, 94 | } 95 | ``` 96 | **For example:** 97 | ```javascript 98 | $pouch.sync('complaints', 'https:/42.233.1.44/complaints', { 99 | selector: { 100 | type: 'complaint', 101 | assignee: this.session.name 102 | } 103 | }); 104 | 105 | ``` 106 | * `$pouch.push(localDatabase, OPTIONAL remoteDatabase, OPTIONAL options)`: The optional remoteDatabase parameter will use the default db set in the pouch options initially. Like https://pouchdb.com/api.html#replication - replicate-to. Also, if the browser has an active session cookie, it will fetch session data (username, etc) from the remote server. 107 | * `$pouch.pull(localDatabase, OPTIONAL remoteDatabase, OPTIONAL options)`: The optional remoteDatabase parameter will use the default db set in the pouch options initially. Like https://pouchdb.com/api.html#replication - replicate-from. Also, if the browser has an active session cookie, it will fetch session data (username, etc) from the remote server. 108 | * `$pouch.changes(OPTIONAL options, OPTIONAL db)`: Listens for change on a db like: https://pouchdb.com/api.html#changes 109 | * `$pouch.put(object, OPTIONAL options, OPTIONAL db)`: https://pouchdb.com/api.html#create_document 110 | * `$pouch.post(object, OPTIONAL options, OPTIONAL db)`: https://pouchdb.com/api.html#create_document 111 | * `$pouch.remove(id, rev, OPTIONAL db)`: https://pouchdb.com/api.html#delete_document 112 | * `$pouch.get(object, OPTIONAL options, OPTIONAL db)`: https://pouchdb.com/api.html#create_document 113 | * `$pouch.query('map/reduce function', OPTIONAL options, OPTIONAL db)`: like https://pouchdb.com/api.html#query_database 114 | * `$pouch.allDocs(OPTIONAL options, OPTIONAL db)`: like https://pouchdb.com/api.html#batch_fetch but `include_docs` is set to true by default. You can however overwrite it of course. 115 | * `$pouch.bulkDocs(docs, OPTIONAL options, OPTIONAL db)`: https://pouchdb.com/api.html#batch_create 116 | * `$pouch.compact(OPTIONAL options, OPTIONAL db)`: https://pouchdb.com/api.html#compaction 117 | * `$pouch.viewCleanup(OPTIONAL db)`: https://pouchdb.com/api.html#view_cleanup 118 | * `$pouch.info(OPTIONAL db)`: like https://pouchdb.com/api.html#database_information 119 | * `$pouch.find(request, OPTIONAL db)`: like https://pouchdb.com/api.html#query_index 120 | * `$pouch.createIndex(index, OPTIONAL db)`: like https://pouchdb.com/api.html#create_index 121 | * `$pouch.putAttachment(docId, [rev], attachmentObject(id,data,type), OPTIONAL db)`: like https://pouchdb.com/api.html#save_attachment 122 | * `$pouch.getAttachment(docId, attachmentId, OPTIONAL db)`: like https://pouchdb.com/api.html#get_attachment 123 | * `$pouch.deleteAttachment(docId, attachmentId, docRev, OPTIONAL db)`: like https://pouchdb.com/api.html#delete_attachment 124 | * `$pouch.close(OPTIONAL db)`: https://pouchdb.com/api.html#close_database 125 | 126 | #### Non-Reactive Properties 127 | * `vm.$databases`: the pouchdb instances which are shared across all components. 128 | 129 | ## Examples 130 | 131 | ```vue 132 | 142 | 143 | 156 | ``` 157 | 158 | ### Reactive & Live Selectors (Mango Queries) 159 | 160 | ```vue 161 | 167 | 168 | 197 | ``` 198 | 199 | ### Single documents 200 | 201 | If you only want to sync a single document that matches a selector, use `first: true`: 202 | 203 | ```javascript 204 | module.exports = { 205 | // ... 206 | pouch: { 207 | projectDetails() { 208 | return { 209 | database: 'mydatabase', 210 | selector: {_id: this.selectedProjectId}, 211 | first: true 212 | } 213 | } 214 | } 215 | // ... 216 | } 217 | ``` 218 | 219 | ### TypeScript 220 | TypeScript example with a TypeScript file to include the pouch-vue plugin and a Single File Component 221 | using the plugin. 222 | 223 | main.ts 224 | ```typescript 225 | 226 | import { Component, Vue } from 'vue-property-decorator'; 227 | import PouchDB from 'pouchdb-browser'; 228 | import lf from 'pouchdb-find'; 229 | import plf from 'pouchdb-live-find'; 230 | import auth from 'pouchdb-authentication'; 231 | 232 | import pouchVue from 'pouch-vue'; 233 | 234 | // PouchDB plugins: pouchdb-find (included in the monorepo) and LiveFind (external plugin) 235 | PouchDB.plugin(lf); 236 | PouchDB.plugin(plf); 237 | PouchDB.plugin(auth); 238 | 239 | Vue.use(pouchVue,{ 240 | pouch: PouchDB, 241 | defaultDB: 'todos', 242 | optionsDB: { 243 | fetch: function (url:any, opts:any) { 244 | opts.credentials = 'include'; 245 | return PouchDB.fetch(url, opts); 246 | } 247 | } 248 | }) 249 | 250 | new Vue({}); 251 | 252 | ``` 253 | Todos.vue 254 | ```vue 255 | 265 | 266 | 284 | 285 | ``` 286 | 287 | ### User Authentication 288 | 289 | ```javascript 290 | this.$pouch.connect(this.credentials.username, this.credentials.password) 291 | .then((res) => { 292 | let isUnauthorized = res.error === 'unauthorized', 293 | isOffline = res.status === 0; 294 | 295 | if (isOffline) { 296 | return; 297 | } 298 | 299 | if (isUnauthorized) { 300 | return; 301 | } 302 | this.$router.push('/dashboard'); 303 | }) 304 | .catch((error) => { 305 | console.error(error); 306 | }); 307 | ``` 308 | 309 | ### Handle Sessions 310 | ```javascript 311 | this.$pouch.getSession().then((data) => { 312 | if (data.status === 0) { 313 | this.$router.push('/login'); 314 | console.log('most likely offline'); 315 | return; 316 | } 317 | 318 | if (!data.user || !data.hasAccess) { 319 | this.$router.push('/login'); 320 | return; 321 | } 322 | 323 | this.$store.commit('UPDATE_SESSION', data); 324 | }); 325 | ``` 326 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouch-vue", 3 | "version": "0.3.5", 4 | "description": "PouchDB bindings for Vue.js", 5 | "main": "lib/index.js", 6 | "types": "types/index.d.ts", 7 | "scripts": { 8 | "build": "rollup -c", 9 | "prepublish": "npm run build", 10 | "prepublishOnly": "npm run build", 11 | "prepare": "npm run build", 12 | "test:unit": "jest --no-cache" 13 | }, 14 | "jest": { 15 | "verbose": true, 16 | "setupFiles": [ 17 | "/tests/global-mocks.js" 18 | ], 19 | "moduleFileExtensions": [ 20 | "js", 21 | "vue" 22 | ], 23 | "moduleNameMapper": { 24 | "^@/(.*)$": "/src/$1" 25 | }, 26 | "transform": { 27 | "^.+\\.js$": "/node_modules/babel-jest", 28 | ".*\\.(vue)$": "/node_modules/vue-jest" 29 | } 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com:MDSLKTR/pouch-vue.git" 34 | }, 35 | "author": "Simon Kunz", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/MDSLKTR/pouch-vue/issues" 39 | }, 40 | "files": [ 41 | "lib", 42 | "types", 43 | "src", 44 | "rollup.config.js" 45 | ], 46 | "keywords": [ 47 | "pouchdb", 48 | "vue", 49 | "vue.js", 50 | "couchdb", 51 | "pouch-vue" 52 | ], 53 | "homepage": "https://github.com/MDSLKTR/pouch-vue", 54 | "dependencies": { 55 | "or": "^0.2.0", 56 | "pouchdb-live-find": "^0.4.0", 57 | "pouchdb-utils": "^8.0.1" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.23.3", 61 | "@babel/preset-env": "^7.23.3", 62 | "@vue/babel-preset-app": "^5.0.8", 63 | "@vue/test-utils": "^2.4.2", 64 | "babel-core": "^7.0.0-bridge.0", 65 | "babel-jest": "^29.7.0", 66 | "babel-preset-env": "^1.7.0", 67 | "cross-env": "^7.0.3", 68 | "eslint": "^8.54.0", 69 | "jest": "^29.7.0", 70 | "node-fetch": "^3.3.2", 71 | "pouchdb-authentication": "^1.1.3", 72 | "pouchdb-node": "^8.0.1", 73 | "rollup": "^4.5.2", 74 | "rollup-plugin-babel": "^4.3.2", 75 | "rollup-plugin-buble": "^0.19.6", 76 | "rollup-plugin-commonjs": "^9.2.2", 77 | "rollup-plugin-json": "^4.0.0", 78 | "rollup-plugin-node-resolve": "^4.0.1", 79 | "rollup-plugin-replace": "^2.1.1", 80 | "vue": "^3.3.8", 81 | "vue-jest": "^3.0.7", 82 | "vue-template-compiler": "^2.7.15" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import json from 'rollup-plugin-json'; 4 | import buble from 'rollup-plugin-buble'; 5 | import replace from 'rollup-plugin-replace'; 6 | 7 | const pkg = require('./package.json'); 8 | 9 | export default { 10 | input: './src/index.js', 11 | output: [ 12 | { file: pkg.main, format: 'umd', name: 'pouchVue' }, 13 | ], 14 | plugins: [ 15 | json(), 16 | resolve(), 17 | commonjs(), 18 | buble(), 19 | replace({ __VERSION__: pkg.version }), 20 | ], 21 | banner: ` 22 | /** 23 | * pouch vue v${pkg.version} 24 | * (c) ${new Date().getFullYear()} Simon Kunz 25 | * @license MIT 26 | */ 27 | `.replace(/ {4}/gm, '').trim(), 28 | }; 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { isRemote } from 'pouchdb-utils'; 2 | 3 | (function() { 4 | let vue = null, 5 | pouch = null, 6 | defaultDB = null, 7 | defaultUsername = null, 8 | defaultPassword = null, 9 | databases = {}, 10 | optionsDB = {}; 11 | 12 | let vuePouch = { 13 | /* Creates a property in 'data' with 'null' value for each pouch property 14 | * defined on the component. This way the user does not have to manually 15 | * define a data property for the reactive databases/selectors. 16 | * 17 | * This partial 'data' object is mixed into the components along with 18 | * the rest of the API (but is empty unless the component has a 'pouch' 19 | * option). 20 | */ 21 | data(vm) { 22 | let pouchOptions = vm.$options.pouch; 23 | if (typeof pouchOptions === 'undefined' || pouchOptions === null) return {}; 24 | if (typeof pouchOptions === 'function') pouchOptions = pouchOptions(vm); 25 | return Object.keys(pouchOptions).reduce((accumulator, currentValue) => { 26 | accumulator[currentValue] = null; 27 | return accumulator 28 | }, {}); 29 | }, 30 | 31 | // lifecycle hooks for mixin 32 | 33 | // now that the data object has been observed and made reactive 34 | // the api can be set up 35 | created() { 36 | if (!vue) { 37 | console.warn('pouch-vue not installed!'); 38 | return; 39 | } 40 | 41 | let vm = this; 42 | 43 | vm._liveFeeds = {}; 44 | 45 | if (defaultDB) { 46 | makeInstance(defaultDB); 47 | } 48 | 49 | function fetchSession(db = databases[defaultDB]) { 50 | return new Promise(resolve => { 51 | db 52 | .getSession() 53 | .then(session => { 54 | db 55 | .getUser(session.userCtx.name) 56 | .then(userData => { 57 | let userObj = Object.assign( 58 | {}, 59 | session.userCtx, 60 | userData 61 | ); 62 | resolve({ 63 | user: userObj, 64 | hasAccess: true, 65 | }); 66 | }) 67 | .catch(error => { 68 | resolve(error); 69 | }); 70 | }) 71 | .catch(error => { 72 | resolve(error); 73 | }); 74 | }); 75 | } 76 | 77 | function login(db = databases[defaultDB]) { 78 | return new Promise(resolve => { 79 | 80 | db 81 | .logIn(defaultUsername, defaultPassword) 82 | .then(user => { 83 | db 84 | .getUser(user.name) 85 | .then(userData => { 86 | let userObj = Object.assign( 87 | {}, 88 | user, 89 | userData 90 | ); 91 | resolve({ 92 | user: userObj, 93 | hasAccess: true, 94 | }); 95 | }) 96 | .catch(error => { 97 | resolve(error); 98 | }); 99 | }) 100 | .catch(error => { 101 | resolve(error); 102 | }); 103 | }); 104 | } 105 | 106 | function makeInstance(db, options = {}) { 107 | // Merge the plugin optionsDB options with those passed in 108 | // when creating pouch dbs. 109 | // Note: default opiontsDB options are passed in when creating 110 | // both local and remote pouch databases. E.g. modifying fetch() 111 | // in the options is only useful for remote Dbs but will be passed 112 | // for local pouch dbs too if set in optionsDB. 113 | // See: https://pouchdb.com/api.html#create_database 114 | 115 | let _options = Object.assign( 116 | {}, 117 | optionsDB, 118 | options 119 | ) 120 | 121 | databases[db] = new pouch(db, _options); 122 | registerListeners(databases[db]); 123 | } 124 | 125 | function registerListeners(db) { 126 | db.on('created', name => { 127 | vm.$emit('pouchdb-db-created', { 128 | db: name, 129 | ok: true, 130 | }); 131 | }); 132 | db.on('destroyed', name => { 133 | vm.$emit('pouchdb-db-destroyed', { 134 | db: name, 135 | ok: true, 136 | }); 137 | }); 138 | } 139 | 140 | let $pouch = { 141 | version: '__VERSION__', 142 | connect(username, password, db=defaultDB) { 143 | if (!databases[db]) { 144 | makeInstance(db); 145 | } 146 | 147 | return new Promise(resolve => { 148 | defaultUsername = username; 149 | defaultPassword = password; 150 | 151 | if (!isRemote(databases[db])) { 152 | resolve({ 153 | message: 'database is not remote', 154 | error: 'bad request', 155 | status: 400, 156 | }); 157 | return; 158 | } 159 | 160 | login(databases[db]).then(res => { 161 | resolve(res); 162 | }); 163 | }); 164 | }, 165 | createUser(username, password, db = defaultDB) { 166 | if (!databases[db]) { 167 | makeInstance(db); 168 | } 169 | return databases[db] 170 | .signUp(username, password) 171 | .then(() => { 172 | return vm.$pouch.connect(username, password, db); 173 | }) 174 | .catch(error => { 175 | return new Promise(resolve => { 176 | resolve(error); 177 | }); 178 | }); 179 | }, 180 | putUser(username, metadata = {}, db=defaultDB) { 181 | if (!databases[db]) { 182 | makeInstance(db); 183 | } 184 | return databases[db] 185 | .putUser(username, { 186 | metadata, 187 | }) 188 | .catch(error => { 189 | return new Promise(resolve => { 190 | resolve(error); 191 | }); 192 | }); 193 | }, 194 | deleteUser(username, db=defaultDB) { 195 | if (!databases[db]) { 196 | makeInstance(db); 197 | } 198 | return databases[db] 199 | .deleteUser(username) 200 | .catch(error => { 201 | return new Promise(resolve => { 202 | resolve(error); 203 | }); 204 | }); 205 | }, 206 | changePassword(username, password, db=defaultDB) { 207 | if (!databases[db]) { 208 | makeInstance(db); 209 | } 210 | return databases[db] 211 | .changePassword(username, password) 212 | .catch(error => { 213 | return new Promise(resolve => { 214 | resolve(error); 215 | }); 216 | }); 217 | }, 218 | changeUsername(oldUsername, newUsername, db=defaultDB) { 219 | if (!databases[db]) { 220 | makeInstance(db); 221 | } 222 | return databases[db] 223 | .changeUsername(oldUsername, newUsername) 224 | .catch(error => { 225 | return new Promise(resolve => { 226 | resolve(error); 227 | }); 228 | }); 229 | }, 230 | signUpAdmin(adminUsername, adminPassword, db=defaultDB) { 231 | if (!databases[db]) { 232 | makeInstance(db); 233 | } 234 | return databases[db] 235 | .signUpAdmin(adminUsername, adminPassword) 236 | .catch(error => { 237 | return new Promise(resolve => { 238 | resolve(error); 239 | }); 240 | }); 241 | }, 242 | deleteAdmin(adminUsername, db=defaultDB) { 243 | if (!databases[db]) { 244 | makeInstance(db); 245 | } 246 | return databases[db] 247 | .deleteAdmin(adminUsername) 248 | .catch(error => { 249 | return new Promise(resolve => { 250 | resolve(error); 251 | }); 252 | }); 253 | }, 254 | disconnect(db=defaultDB) { 255 | if (!databases[db]) { 256 | makeInstance(db); 257 | } 258 | return new Promise(resolve => { 259 | defaultUsername = null; 260 | defaultPassword = null; 261 | 262 | if (!isRemote(databases[db])) { 263 | resolve({ 264 | message: 'database is not remote', 265 | error: 'bad request', 266 | status: 400, 267 | }); 268 | return; 269 | } 270 | 271 | databases[db] 272 | .logOut() 273 | .then(res => { 274 | resolve({ 275 | ok: res.ok, 276 | user: null, 277 | hasAccess: false, 278 | }); 279 | }) 280 | .catch(error => { 281 | resolve(error); 282 | }); 283 | }); 284 | }, 285 | 286 | destroy(db=defaultDB) { 287 | if (!databases[db]) { 288 | makeInstance(db); 289 | } 290 | 291 | return databases[db].destroy().then(() => { 292 | if (db !== defaultDB) { 293 | delete databases[db]; 294 | } 295 | }); 296 | }, 297 | 298 | defaults(options = {}) { 299 | pouch.defaults(options); 300 | }, 301 | 302 | close(db=defaultDB) { 303 | if (!databases[db]) { 304 | makeInstance(db); 305 | } 306 | 307 | return databases[db].close().then(() => { 308 | if (db !== defaultDB) { 309 | delete databases[db]; 310 | } 311 | }); 312 | }, 313 | 314 | getSession(db=defaultDB) { 315 | if (!databases[db]) { 316 | makeInstance(db); 317 | } 318 | if (!isRemote(databases[db])) { 319 | return new Promise(resolve => { 320 | resolve({ 321 | message: 'database is not remote', 322 | error: 'bad request', 323 | status: 400, 324 | }); 325 | }); 326 | } 327 | return fetchSession(databases[db]); 328 | }, 329 | 330 | sync(localDB, remoteDB=defaultDB, options = {}) { 331 | if (!databases[localDB]) { 332 | makeInstance(localDB); 333 | } 334 | if (!databases[remoteDB]) { 335 | makeInstance(remoteDB); 336 | } 337 | if (!defaultDB) { 338 | defaultDB = remoteDB; 339 | } 340 | 341 | let _options = Object.assign( 342 | {}, 343 | { 344 | live: true, 345 | retry: true, 346 | back_off_function: delay => { 347 | if (delay === 0) { 348 | return 1000; 349 | } 350 | return delay * 3; 351 | }, 352 | }, 353 | options 354 | ); 355 | 356 | let sync = pouch 357 | .sync(databases[localDB], databases[remoteDB], _options) 358 | .on('paused', err => { 359 | if (err) { 360 | vm.$emit('pouchdb-sync-error', { 361 | db: localDB, 362 | error: err, 363 | }); 364 | return; 365 | } 366 | else { 367 | 368 | vm.$emit('pouchdb-sync-paused', { 369 | db: localDB, 370 | paused: true, 371 | }); 372 | } 373 | }) 374 | .on('change', info => { 375 | vm.$emit('pouchdb-sync-change', { 376 | db: localDB, 377 | info: info, 378 | }); 379 | }) 380 | .on('active', () => { 381 | vm.$emit('pouchdb-sync-active', { 382 | db: localDB, 383 | active: true, 384 | }); 385 | }) 386 | .on('denied', err => { 387 | vm.$emit('pouchdb-sync-denied', { 388 | db: localDB, 389 | error: err, 390 | }); 391 | }) 392 | .on('complete', info => { 393 | vm.$emit('pouchdb-sync-complete', { 394 | db: localDB, 395 | info: info, 396 | }); 397 | }) 398 | .on('error', err => { 399 | vm.$emit('pouchdb-sync-error', { 400 | db: localDB, 401 | error: err, 402 | }); 403 | }); 404 | 405 | return sync; 406 | }, 407 | push(localDB, remoteDB=defaultDB, options = {}) { 408 | if (!databases[localDB]) { 409 | makeInstance(localDB); 410 | } 411 | if (!databases[remoteDB]) { 412 | makeInstance(remoteDB); 413 | } 414 | if (!defaultDB) { 415 | defaultDB = remoteDB; 416 | } 417 | 418 | let _options = Object.assign( 419 | {}, 420 | { 421 | live: true, 422 | retry: true, 423 | back_off_function: delay => { 424 | if (delay === 0) { 425 | return 1000; 426 | } 427 | return delay * 3; 428 | }, 429 | }, 430 | options 431 | ); 432 | 433 | let rep = databases[localDB].replicate 434 | .to(databases[remoteDB], options) 435 | .on('paused', err => { 436 | if (err) { 437 | vm.$emit('pouchdb-push-error', { 438 | db: localDB, 439 | error: err, 440 | }); 441 | return; 442 | } 443 | else { 444 | vm.$emit('pouchdb-push-paused', { 445 | db: localDB, 446 | paused: true, 447 | }); 448 | } 449 | }) 450 | .on('change', info => { 451 | vm.$emit('pouchdb-push-change', { 452 | db: localDB, 453 | info: info, 454 | }); 455 | }) 456 | .on('active', () => { 457 | vm.$emit('pouchdb-push-active', { 458 | db: localDB, 459 | active: true, 460 | }); 461 | }) 462 | .on('denied', err => { 463 | vm.$emit('pouchdb-push-denied', { 464 | db: localDB, 465 | error: err, 466 | }); 467 | }) 468 | .on('complete', info => { 469 | vm.$emit('pouchdb-push-complete', { 470 | db: localDB, 471 | info: info, 472 | }); 473 | }) 474 | .on('error', err => { 475 | vm.$emit('pouchdb-push-error', { 476 | db: localDB, 477 | error: err, 478 | }); 479 | }); 480 | 481 | return rep; 482 | }, 483 | 484 | pull(localDB, remoteDB=defaultDB, options = {}) { 485 | if (!databases[localDB]) { 486 | makeInstance(localDB); 487 | } 488 | if (!databases[remoteDB]) { 489 | makeInstance(remoteDB); 490 | } 491 | if (!defaultDB) { 492 | defaultDB = remoteDB; 493 | } 494 | 495 | let _options = Object.assign( 496 | {}, 497 | { 498 | live: true, 499 | retry: true, 500 | back_off_function: delay => { 501 | if (delay === 0) { 502 | return 1000; 503 | } 504 | return delay * 3; 505 | }, 506 | }, 507 | options 508 | ); 509 | 510 | let rep = databases[localDB].replicate 511 | .from(databases[remoteDB], options) 512 | .on('paused', err => { 513 | if (err) { 514 | vm.$emit('pouchdb-pull-error', { 515 | db: localDB, 516 | error: err, 517 | }); 518 | return; 519 | } 520 | else { 521 | vm.$emit('pouchdb-pull-paused', { 522 | db: localDB, 523 | paused: true, 524 | }); 525 | } 526 | }) 527 | .on('change', info => { 528 | vm.$emit('pouchdb-pull-change', { 529 | db: localDB, 530 | info: info, 531 | }); 532 | }) 533 | .on('active', () => { 534 | vm.$emit('pouchdb-pull-active', { 535 | db: localDB, 536 | active: true, 537 | }); 538 | }) 539 | .on('denied', err => { 540 | vm.$emit('pouchdb-pull-denied', { 541 | db: localDB, 542 | error: err, 543 | }); 544 | }) 545 | .on('complete', info => { 546 | vm.$emit('pouchdb-pull-complete', { 547 | db: localDB, 548 | info: info, 549 | }); 550 | }) 551 | .on('error', err => { 552 | vm.$emit('pouchdb-pull-error', { 553 | db: localDB, 554 | error: err, 555 | }); 556 | }); 557 | 558 | return rep; 559 | }, 560 | 561 | changes(options = {}, db=defaultDB) { 562 | if (!databases[db]) { 563 | makeInstance(db); 564 | } 565 | 566 | let _options = Object.assign( 567 | {}, 568 | { 569 | live: true, 570 | retry: true, 571 | back_off_function: delay => { 572 | if (delay === 0) { 573 | return 1000; 574 | } 575 | return delay * 3; 576 | }, 577 | }, 578 | options 579 | ); 580 | 581 | let changes = databases[db] 582 | .changes(_options) 583 | .on('change', info => { 584 | vm.$emit('pouchdb-changes-change', { 585 | db: db, 586 | info: info, 587 | }); 588 | }) 589 | .on('complete', info => { 590 | vm.$emit('pouchdb-changes-complete', { 591 | db: db, 592 | info: info, 593 | }); 594 | }) 595 | .on('error', err => { 596 | vm.$emit('pouchdb-changes-error', { 597 | db: db, 598 | error: err, 599 | }); 600 | }); 601 | 602 | return changes; 603 | }, 604 | 605 | get(object, options = {}, db=defaultDB) { 606 | if (!databases[db]) { 607 | makeInstance(db); 608 | } 609 | return databases[db].get(object, options); 610 | }, 611 | 612 | put(object, options = {}, db=defaultDB) { 613 | if (!databases[db]) { 614 | makeInstance(db); 615 | } 616 | return databases[db].put(object, options); 617 | }, 618 | 619 | post(object, options = {}, db=defaultDB) { 620 | if (!databases[db]) { 621 | makeInstance(db); 622 | } 623 | return databases[db].post(object, options); 624 | }, 625 | 626 | remove(object, options = {}, db=defaultDB) { 627 | if (!databases[db]) { 628 | makeInstance(db); 629 | } 630 | return databases[db].remove(object, options); 631 | }, 632 | 633 | query(fun, options = {}, db=defaultDB) { 634 | if (!databases[db]) { 635 | makeInstance(db); 636 | } 637 | return databases[db].query(fun, options); 638 | }, 639 | 640 | find(options, db=defaultDB) { 641 | if (!databases[db]) { 642 | makeInstance(db); 643 | } 644 | 645 | return databases[db].find(options); 646 | }, 647 | 648 | createIndex(index, db=defaultDB) { 649 | if (!databases[db]) { 650 | makeInstance(db); 651 | } 652 | 653 | return databases[db].createIndex(index); 654 | }, 655 | 656 | allDocs(options = {}, db=defaultDB) { 657 | if (!databases[db]) { 658 | makeInstance(db); 659 | } 660 | 661 | let _options = Object.assign( 662 | {}, 663 | { include_docs: true }, 664 | options 665 | ); 666 | 667 | return databases[db].allDocs(_options); 668 | }, 669 | 670 | bulkDocs(docs, options = {}, db=defaultDB) { 671 | if (!databases[db]) { 672 | makeInstance(db); 673 | } 674 | 675 | return databases[db].bulkDocs(docs, options); 676 | }, 677 | 678 | compact(options = {}, db=defaultDB) { 679 | if (!databases[db]) { 680 | makeInstance(db); 681 | } 682 | 683 | return databases[db].compact(options); 684 | }, 685 | 686 | viewCleanup(db=defaultDB) { 687 | if (!databases[db]) { 688 | makeInstance(db); 689 | } 690 | 691 | return databases[db].viewCleanup(); 692 | }, 693 | 694 | info(db=defaultDB) { 695 | if (!databases[db]) { 696 | makeInstance(db); 697 | } 698 | 699 | return databases[db].info(); 700 | }, 701 | 702 | putAttachment(docId, rev, attachment, db=defaultDB) { 703 | if (!databases[db]) { 704 | makeInstance(db); 705 | } 706 | 707 | return databases[db].putAttachment( 708 | docId, 709 | attachment.id, 710 | rev ? rev : null, 711 | attachment.data, 712 | attachment.type 713 | ); 714 | }, 715 | 716 | getAttachment(docId, attachmentId, db=defaultDB) { 717 | if (!databases[db]) { 718 | makeInstance(db); 719 | } 720 | 721 | return databases[db].getAttachment(docId, attachmentId); 722 | }, 723 | 724 | deleteAttachment(docId, attachmentId, docRev, db=defaultDB) { 725 | if (!databases[db]) { 726 | makeInstance(db); 727 | } 728 | 729 | return databases[db].removeAttachment( 730 | docId, 731 | attachmentId, 732 | docRev 733 | ); 734 | }, 735 | }; 736 | 737 | // add non reactive api 738 | vm.$pouch = $pouch; 739 | //add non reactive property 740 | vm.$databases = databases; // Add non-reactive property 741 | 742 | let pouchOptions = this.$options.pouch; 743 | 744 | if (!pouchOptions) { 745 | return; 746 | } 747 | 748 | if (typeof pouchOptions === 'function') { 749 | pouchOptions = pouchOptions(); 750 | } 751 | 752 | Object.keys(pouchOptions).map(key => { 753 | let pouchFn = pouchOptions[key]; 754 | if (typeof pouchFn !== 'function') { 755 | pouchFn = () => { 756 | return pouchOptions[key]; 757 | }; 758 | } 759 | 760 | // if the selector changes, modify the liveFeed object 761 | // 762 | vm.$watch( 763 | pouchFn, 764 | config => { 765 | // if the selector is now giving a value of null or undefined, then return 766 | // the previous liveFeed object will remain 767 | if (!config) { 768 | vm.$emit('pouchdb-livefeed-error', { 769 | db: key, 770 | config: config, 771 | error: 'Null or undefined selector', 772 | }); 773 | 774 | return; 775 | } 776 | 777 | 778 | let selector, sort, skip, limit, first; 779 | 780 | if (config.selector) { 781 | selector = config.selector; 782 | sort = config.sort; 783 | skip = config.skip; 784 | limit = config.limit; 785 | first = config.first; 786 | } else { 787 | selector = config; 788 | } 789 | 790 | // the database could change in the config options 791 | // so the key could point to a database of a different name 792 | let databaseParam = config.database || key; 793 | let db = null; 794 | 795 | if (typeof databaseParam === 'object') { 796 | db = databaseParam; 797 | } else if (typeof databaseParam === 'string') { 798 | if (!databases[databaseParam]) { 799 | makeInstance(databaseParam); 800 | } 801 | db = databases[databaseParam]; 802 | } 803 | if (!db) { 804 | vm.$emit('pouchdb-livefeed-error', { 805 | db: key, 806 | error: 'Null or undefined database', 807 | }); 808 | return; 809 | } 810 | if (vm._liveFeeds[key]) { 811 | vm._liveFeeds[key].cancel(); 812 | } 813 | let aggregateCache = []; 814 | 815 | // the LiveFind plugin returns a liveFeed object 816 | vm._liveFeeds[key] = db 817 | .liveFind({ 818 | selector: selector, 819 | sort: sort, 820 | skip: skip, 821 | limit: limit, 822 | aggregate: true, 823 | }) 824 | .on('update', (update, aggregate) => { 825 | if (first && aggregate) 826 | aggregate = aggregate[0]; 827 | 828 | vm.$data[key] = aggregateCache = aggregate; 829 | 830 | vm.$emit('pouchdb-livefeed-update', { 831 | db: key, 832 | name: db.name, 833 | }); 834 | 835 | }) 836 | .on('ready', () => { 837 | vm.$data[key] = aggregateCache; 838 | 839 | vm.$emit('pouchdb-livefeed-ready', { 840 | db: key, 841 | name: db.name, 842 | }); 843 | }) 844 | .on('cancelled', function() { 845 | vm.$emit('pouchdb-livefeed-cancel', { 846 | db: key, 847 | name: db.name, 848 | }); 849 | }) 850 | .on('error', function(err) { 851 | vm.$emit('pouchdb-livefeed-error', { 852 | db: key, 853 | name: db.name, 854 | error: err, 855 | }); 856 | }); 857 | }, 858 | { 859 | immediate: true, 860 | } 861 | ); 862 | }); 863 | }, 864 | // tear down the liveFeed objects 865 | beforeDestroy() { 866 | Object.keys(this._liveFeeds).map(lfKey => { 867 | this._liveFeeds[lfKey].cancel(); 868 | }); 869 | }, 870 | }; 871 | 872 | let api = { 873 | install: (Vue, options = {}) => { 874 | vue = Vue; 875 | 876 | ({ pouch = PouchDB, defaultDB = '', optionsDB = {} } = options); 877 | 878 | // In PouchDB v7.0.0 the debug() API was moved to a separate plugin. 879 | // var pouchdbDebug = require('pouchdb-debug'); 880 | // PouchDB.plugin(pouchdbDebug); 881 | if (options.debug === '*') pouch.debug.enable('*'); 882 | 883 | Vue.mixin(vuePouch); 884 | }, 885 | }; 886 | 887 | module.exports = api; 888 | })(); 889 | -------------------------------------------------------------------------------- /tests/ExistingTodosDataFunction.vue: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/TodosDataFunctionWithSelector.vue: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/emptyDataFunction.vue: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/emptyDataObject.vue: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/global-mocks.js: -------------------------------------------------------------------------------- 1 | const fetchPolyfill = require('node-fetch') 2 | global.fetch = fetchPolyfill.fetch 3 | global.Request = fetchPolyfill.Request 4 | global.Headers = fetchPolyfill.Headers 5 | global.Response = fetchPolyfill.Response -------------------------------------------------------------------------------- /tests/noDataFunctionOrObject.vue: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/testData.spec.js: -------------------------------------------------------------------------------- 1 | import PouchDB from 'pouchdb-node'; 2 | import PouchVue from '../src/index' 3 | import lf from 'pouchdb-find'; 4 | import plf from 'pouchdb-live-find'; 5 | 6 | // vue-test-utils 7 | import { createLocalVue, mount } from '@vue/test-utils' 8 | 9 | // import test vue single file components 10 | import emptyDataFunction from './emptyDataFunction.vue' 11 | import emptyDataObject from './emptyDataObject.vue' 12 | import noData from './noDataFunctionOrObject.vue' 13 | import existingData from './ExistingTodosDataFunction.vue' 14 | import todosDataWithSelector from './TodosDataFunctionWithSelector.vue' 15 | 16 | describe('Pouch options are returned by function', () => { 17 | describe('Unit Tests that todos is defined on Vue components', () => { 18 | var testDatum = [ 19 | { name: 'Test Plugin with Empty Data Function', component: emptyDataFunction }, 20 | { name: 'Test Plugin with Empty Data Object', component: emptyDataObject }, 21 | { name: 'Test Plugin with No Data Function Or Object', component: noData }, 22 | { name: 'Test Plugin with Existing Data Function', component: existingData } 23 | ]; 24 | 25 | for (var i = 0; i < testDatum.length; i++) { 26 | 27 | 28 | let tryTestData = testDatum[i].component; 29 | let tryTestName = testDatum[i].name; 30 | 31 | function testFunc() { 32 | const localVue = createLocalVue() 33 | 34 | // add requisite PouchDB plugins 35 | PouchDB.plugin(lf); 36 | PouchDB.plugin(plf); 37 | 38 | // add Vue.js plugin 39 | localVue.use(PouchVue, { 40 | pouch: PouchDB, 41 | defaultDB: 'farfromhere', 42 | }); 43 | 44 | 45 | const wrapper = mount(tryTestData, { 46 | localVue, 47 | pouch() { 48 | return { 49 | todos: {/*empty selector*/ } 50 | } 51 | } 52 | }) 53 | 54 | expect(wrapper.vm.$data.todos).not.toBeUndefined(); 55 | } 56 | test(tryTestName, testFunc); 57 | } 58 | }) 59 | 60 | describe('Unit Tests to see that the todos property on the data root level is connected with the todos property on the vue instance (this is what the beforeCreate lifecycle hook does)', () => { 61 | var testDatum = [ 62 | { name: 'Test Plugin with Empty Data Function', component: emptyDataFunction }, 63 | { name: 'Test Plugin with Empty Data Object', component: emptyDataObject }, 64 | { name: 'Test Plugin with No Data Function Or Object', component: noData }, 65 | { name: 'Test Plugin with Existing Data Function', component: existingData } 66 | ]; 67 | 68 | for (var i = 0; i < testDatum.length; i++) { 69 | 70 | 71 | let tryTestData = testDatum[i].component; 72 | let tryTestName = testDatum[i].name; 73 | 74 | function testFunc() { 75 | const localVue = createLocalVue() 76 | 77 | // add requisite PouchDB plugins 78 | PouchDB.plugin(lf); 79 | PouchDB.plugin(plf); 80 | 81 | // add Vue.js plugin 82 | localVue.use(PouchVue, { 83 | pouch: PouchDB, 84 | defaultDB: 'farfromhere', 85 | }); 86 | 87 | 88 | const wrapper = mount(tryTestData, { 89 | localVue, 90 | pouch() { 91 | return { 92 | todos: {/*empty selector*/ } 93 | } 94 | } 95 | }) 96 | 97 | wrapper.vm.todos = ['north', 'east', 'south', 'west']; 98 | 99 | expect(wrapper.vm.$data.todos).toBe(wrapper.vm.todos); 100 | 101 | } 102 | test(tryTestName, testFunc); 103 | } 104 | 105 | }) 106 | }) 107 | 108 | describe('Pouch options are objects', () => { 109 | describe('Unit Tests that todos is defined on Vue components', () => { 110 | var testDatum = [ 111 | { name: 'Test Plugin with Empty Data Function', component: emptyDataFunction }, 112 | { name: 'Test Plugin with Empty Data Object', component: emptyDataObject }, 113 | { name: 'Test Plugin with No Data Function Or Object', component: noData }, 114 | { name: 'Test Plugin with Existing Data Function', component: existingData } 115 | ]; 116 | 117 | for (var i = 0; i < testDatum.length; i++) { 118 | 119 | 120 | let tryTestData = testDatum[i].component; 121 | let tryTestName = testDatum[i].name; 122 | 123 | function testFunc() { 124 | const localVue = createLocalVue() 125 | 126 | // add requisite PouchDB plugins 127 | PouchDB.plugin(lf); 128 | PouchDB.plugin(plf); 129 | 130 | // add Vue.js plugin 131 | localVue.use(PouchVue, { 132 | pouch: PouchDB, 133 | defaultDB: 'farfromhere', 134 | }); 135 | 136 | 137 | const wrapper = mount(tryTestData, { 138 | localVue, 139 | pouch: { 140 | todos: {/*empty selector*/ } 141 | } 142 | }) 143 | 144 | expect(wrapper.vm.$data.todos).not.toBeUndefined(); 145 | } 146 | test(tryTestName, testFunc); 147 | } 148 | }) 149 | 150 | describe('Unit Tests to see that the todos property on the data root level is connected with the todos property on the vue instance (this is what the beforeCreate lifecycle hook does)', () => { 151 | var testDatum = [ 152 | { name: 'Test Plugin with Empty Data Function', component: emptyDataFunction }, 153 | { name: 'Test Plugin with Empty Data Object', component: emptyDataObject }, 154 | { name: 'Test Plugin with No Data Function Or Object', component: noData }, 155 | { name: 'Test Plugin with Existing Data Function', component: existingData } 156 | ]; 157 | 158 | for (var i = 0; i < testDatum.length; i++) { 159 | 160 | 161 | let tryTestData = testDatum[i].component; 162 | let tryTestName = testDatum[i].name; 163 | 164 | function testFunc() { 165 | const localVue = createLocalVue() 166 | 167 | // add requisite PouchDB plugins 168 | PouchDB.plugin(lf); 169 | PouchDB.plugin(plf); 170 | 171 | // add Vue.js plugin 172 | localVue.use(PouchVue, { 173 | pouch: PouchDB, 174 | defaultDB: 'farfromhere', 175 | }); 176 | 177 | 178 | const wrapper = mount(tryTestData, { 179 | localVue, 180 | pouch: { 181 | todos: {/*empty selector*/ } 182 | } 183 | }) 184 | 185 | wrapper.vm.todos = ['north', 'east', 'south', 'west']; 186 | 187 | expect(wrapper.vm.$data.todos).toBe(wrapper.vm.todos); 188 | 189 | } 190 | test(tryTestName, testFunc); 191 | } 192 | 193 | }) 194 | }) 195 | describe('Set selector to null', () => { 196 | var testDatum = [ 197 | { name: 'Test Plugin with Reactive Selector that can return null', component: todosDataWithSelector }, 198 | ]; 199 | 200 | for (var i = 0; i < testDatum.length; i++) { 201 | 202 | 203 | let tryTestData = testDatum[i].component; 204 | let tryTestName = testDatum[i].name; 205 | 206 | // selector will return null if the age is less than the max age 207 | // purely to get a reactive selector that will return null occasionally and 208 | // trip up the watcher on the pouch database config options 209 | let selector = function () { return (this.age < this.maxAge) ? null : {} } 210 | 211 | function testFunc(done) { 212 | const localVue = createLocalVue() 213 | 214 | // add requisite PouchDB plugins 215 | PouchDB.plugin(lf); 216 | PouchDB.plugin(plf); 217 | 218 | // add Vue.js plugin 219 | localVue.use(PouchVue, { 220 | pouch: PouchDB, 221 | defaultDB: 'farfromhere', 222 | }); 223 | 224 | 225 | const wrapper = mount(tryTestData, { 226 | localVue, 227 | pouch: { 228 | todos: selector 229 | } 230 | }) 231 | 232 | wrapper.vm.todos = ['north', 'east', 'south', 'west']; 233 | 234 | wrapper.vm.maxAge = 50; 235 | 236 | //watchers are deferred to the next update cycle that Vue uses to look for changes. 237 | //the change to the selector has a watcher on it in pouch-vue 238 | wrapper.vm.$nextTick(() => { 239 | expect(wrapper.emitted('pouchdb-livefeed-error')).toHaveLength(1); 240 | done(); 241 | }); 242 | } 243 | test(tryTestName, testFunc); 244 | } 245 | }) 246 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | import _Vue, { PluginObject } from 'vue'; 9 | declare type PouchDatabases = Record; 10 | interface PouchVueOptions { 11 | pouch: PouchDB.Static; 12 | defaultDB: string; 13 | debug: string; 14 | optionsDB: any; 15 | } 16 | interface PouchAPI { 17 | version: string; 18 | connect(username: string, password: string, db?: string): Promise; 19 | createUser(username: string, password: string, db?: string): Promise; 20 | putUser(username: string, metadata?: {}, db?: string): Promise<{} | PouchDB.Core.Response>; 21 | deleteUser(username: string, db?: string): Promise<{} | PouchDB.Core.Response>; 22 | changePassword(username: string, password: string, db?: string): Promise<{} | PouchDB.Core.Response>; 23 | changeUsername(oldUsername: string, newUsername: string, db?: string): Promise<{} | PouchDB.Core.Response>; 24 | signUpAdmin(adminUsername: string, adminPassword: string, db?: string): Promise; 25 | deleteAdmin(adminUsername: string, db?: string): Promise; 26 | disconnect(db?: string): void; 27 | destroy(db?: string): void; 28 | defaults(options: PouchDB.Configuration.DatabaseConfiguration): void; 29 | close(db?: string): Promise; 30 | getSession(db?: string): Promise<{}>; 31 | sync(localDB: string, remoteDB?: string, options?: {}): PouchDB.Replication.Sync<{}>; 32 | push(localDB: string, remoteDB?: string, options?: {}): PouchDB.Replication.Replication<{}>; 33 | pull(localDB: string, remoteDB?: string, options?: {}): PouchDB.Replication.Replication<{}>; 34 | changes(options?: {}, db?: string): PouchDB.Core.Changes<{}>; 35 | get(object: any, options?: PouchDB.Core.GetOptions, db?: string): Promise; 36 | put(object: any, options?: PouchDB.Core.PutOptions, db?: string): Promise; 37 | post(object: any, options?: PouchDB.Core.Options, db?: string): Promise; 38 | remove(object: any, options?: PouchDB.Core.Options, db?: string): Promise; 39 | query(fun: any, options?: PouchDB.Query.Options<{}, {}>, db?: string): Promise>; 40 | find(options: PouchDB.Find.FindRequest<{}>, db?: string): Promise>; 41 | createIndex(index: PouchDB.Find.CreateIndexOptions, db?: string): Promise>; 42 | allDocs(options?: PouchDB.Core.AllDocsWithKeyOptions | PouchDB.Core.AllDocsWithKeysOptions | PouchDB.Core.AllDocsWithinRangeOptions | PouchDB.Core.AllDocsOptions, db?: string): Promise>; 43 | bulkDocs(docs: PouchDB.Core.PutDocument<{}>[], options?: PouchDB.Core.BulkDocsOptions, db?: string): Promise<(PouchDB.Core.Response | PouchDB.Core.Error)[]>; 44 | compact(options?: PouchDB.Core.CompactOptions, db?: string): Promise; 45 | viewCleanup(db?: string): Promise; 46 | info(db?: string): Promise; 47 | putAttachment(docId: PouchDB.Core.DocumentId, rev: string, attachment: { 48 | id: string; 49 | data: PouchDB.Core.AttachmentData; 50 | type: string; 51 | }, db?: string): Promise; 52 | getAttachment(docId: PouchDB.Core.DocumentId, attachmentId: PouchDB.Core.AttachmentId, db?: string): Promise; 53 | deleteAttachment(docId: PouchDB.Core.DocumentId, attachmentId: PouchDB.Core.AttachmentId, docRev: PouchDB.Core.RevisionId, db?: string): Promise; 54 | } 55 | declare module 'vue/types/vue' { 56 | interface Vue { 57 | $pouch: PouchAPI; 58 | $databases: PouchDatabases; 59 | _liveFeeds: Record; 60 | options: any; 61 | } 62 | } 63 | declare module 'vue/types/options' { 64 | interface ComponentOptions { 65 | pouch?: any; 66 | } 67 | } 68 | declare let api: PluginObject; 69 | export default api; 70 | --------------------------------------------------------------------------------