├── .eslintignore ├── .eslintrc ├── .gitignore ├── .istanbul.yml ├── .travis.yml ├── README.md ├── client.js ├── index.js ├── package.json ├── server.js └── test ├── client.js ├── server.js └── tandem.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | test -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments 3 | "browser": true, // browser global variables 4 | "node": true // Node.js global variables and Node.js-specific rules 5 | }, 6 | "ecmaFeatures": { 7 | "arrowFunctions": true, 8 | "blockBindings": true, 9 | "classes": true, 10 | "defaultParams": true, 11 | "destructuring": true, 12 | "forOf": true, 13 | "generators": false, 14 | "modules": true, 15 | "objectLiteralComputedProperties": true, 16 | "objectLiteralDuplicateProperties": false, 17 | "objectLiteralShorthandMethods": true, 18 | "objectLiteralShorthandProperties": true, 19 | "spread": true, 20 | "superInFunctions": true, 21 | "templateStrings": true, 22 | "jsx": true 23 | }, 24 | "rules": { 25 | 26 | /** 27 | * Variables 28 | */ 29 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 30 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 31 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 32 | "vars": "local", 33 | "args": "after-used" 34 | }], 35 | "no-use-before-define": 0, // http://eslint.org/docs/rules/no-use-before-define 36 | 37 | /** 38 | * Possible errors 39 | */ 40 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle 41 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 42 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 43 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 44 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 45 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 46 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 47 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 48 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 49 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 50 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 51 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 52 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 53 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 54 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 55 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 56 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 57 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 58 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 59 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var 60 | 61 | /** 62 | * Best practices 63 | */ 64 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 65 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 66 | "default-case": 2, // http://eslint.org/docs/rules/default-case 67 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 68 | "allowKeywords": true 69 | }], 70 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 71 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in 72 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 73 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 74 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 75 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 76 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 77 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 78 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 79 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 80 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 81 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 82 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 83 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 84 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 85 | "no-new": 2, // http://eslint.org/docs/rules/no-new 86 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 87 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 88 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 89 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 90 | "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign 91 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 92 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 93 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 94 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 95 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 96 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 97 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 98 | "no-with": 2, // http://eslint.org/docs/rules/no-with 99 | "radix": 2, // http://eslint.org/docs/rules/radix 100 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 101 | "yoda": 2, // http://eslint.org/docs/rules/yoda 102 | 103 | /** 104 | * Style 105 | */ 106 | "indent": [2, 2], // http://eslint.org/docs/rules/indent 107 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 108 | "1tbs", { 109 | "allowSingleLine": true 110 | }], 111 | "quotes": [ 112 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 113 | ], 114 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 115 | "properties": "never" 116 | }], 117 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 118 | "before": false, 119 | "after": true 120 | }], 121 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 122 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 123 | "func-names": 1, // http://eslint.org/docs/rules/func-names 124 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 125 | "beforeColon": false, 126 | "afterColon": true 127 | }], 128 | "new-cap": 0, // http://eslint.org/docs/rules/new-cap 129 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 130 | "max": 2 131 | }], 132 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 133 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 134 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 135 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 136 | "no-extra-parens": [2, "functions"], // http://eslint.org/docs/rules/no-extra-parens 137 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 138 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 139 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 140 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 141 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 142 | "before": false, 143 | "after": true 144 | }], 145 | "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords 146 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 147 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 148 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 149 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 150 | "spaced-comment": [2, "always", {// http://eslint.org/docs/rules/spaced-comment 151 | "exceptions": ["-", "+"], 152 | "markers": ["=", "!"] // space here to support sprockets directives 153 | }] 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | 4 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | excludes: ['test', 'node_modules'] 3 | check: 4 | global: 5 | lines: 100 6 | branches: 100 7 | statements: 100 8 | functions: 100 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | - 5 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pouch-stream-multi-sync 2 | 3 | [![By](https://img.shields.io/badge/made%20by-yld!-32bbee.svg?style=flat)](http://yld.io/contact?source=github-pouch-stream-multi-sync) 4 | [![Build Status](https://secure.travis-ci.org/pgte/pouch-stream-multi-sync.svg?branch=master)](http://travis-ci.org/pgte/pouch-stream-multi-sync?branch=master) 5 | 6 | Sync several PouchDBs through a stream. 7 | 8 | Supports reconnection, negotiation and authentication. 9 | 10 | ## Install 11 | 12 | ``` 13 | $ npm install pouch-stream-multi-sync --save 14 | ``` 15 | 16 | ## Server 17 | 18 | ```js 19 | var PouchSync = require('pouch-stream-multi-sync'); 20 | 21 | var server = PouchSync.createServer(onRequest); 22 | 23 | function onRequest(credentials, dbName, cb) { 24 | if (credentials.token == 'some token') { 25 | cb(null, new PouchDB(dbName)); 26 | } else { 27 | cb(new Error('not allowed')); 28 | } 29 | }; 30 | 31 | // pipe server into and from duplex stream 32 | 33 | stream.pipe(server).pipe(stream); 34 | ``` 35 | 36 | ## Client 37 | 38 | Example of client using a websocket: 39 | 40 | ```js 41 | var websocket = require('websocket-stream'); 42 | var PouchSync = require('pouch-stream-multi-sync'); 43 | 44 | var db = new PouchDB('todos'); 45 | var client = PouchSync.createClient(getStream); 46 | var sync = client.sync(db, { 47 | remoteName: 'todos-server', 48 | credentials: { token: 'some token'} 49 | }); 50 | 51 | client.connect('ws://somehost:someport'); 52 | 53 | function getStream(address) { 54 | return websocket(address); 55 | } 56 | ``` 57 | 58 | ## API 59 | 60 | TODO 61 | 62 | 63 | ## License 64 | 65 | ISC 66 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('pouch-stream-multi-sync:client'); 2 | var extend = require('xtend'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var Reconnect = require('reconnect-core'); 5 | var PipeChannels = require('pipe-channels'); 6 | var PouchRemoteStream = require('pouch-remote-stream'); 7 | // var timers = require('timers'); 8 | 9 | var interestingSyncEvents = [ 10 | 'change', 'paused', 'active', 'denied', 'complete', 'error']; 11 | 12 | var interestingReconnectEvents = [ 13 | 'connect', 'reconnect', 'disconnect']; 14 | 15 | module.exports = createClient; 16 | 17 | function createClient(createStream) { 18 | var reconnect = Reconnect(createStream); 19 | 20 | var PouchDB; 21 | var channels; 22 | var syncs = []; 23 | var r = reconnect(handleStream); 24 | r.on('error', propagateError); 25 | 26 | var client = new EventEmitter(); 27 | client.connect = connect; 28 | client.sync = sync; 29 | client.destroy = client.end = destroy; 30 | 31 | interestingReconnectEvents.forEach(function eachEvent(event) { 32 | r.on(event, function onEvent(payload) { 33 | client.emit(event, payload); 34 | }); 35 | }); 36 | 37 | return client; 38 | 39 | // ----------------- 40 | 41 | function connect() { 42 | debug('connect, args = %j', arguments); 43 | r.reconnect = true; 44 | r.connect.apply(r, arguments); 45 | return client; 46 | } 47 | 48 | function handleStream(stream) { 49 | debug('handleStream'); 50 | stream.on('error', propagateError); 51 | channels = PipeChannels.createClient(); 52 | channels.on('error', propagateError); 53 | stream.pipe(channels).pipe(stream); 54 | setupSyncs(); 55 | } 56 | 57 | function sync(db, _options) { 58 | var options = extend({}, { 59 | remoteName: db._db_name, 60 | }, _options); 61 | 62 | debug('sync for db %s, options = %j', db._db_name, options); 63 | 64 | /* istanbul ignore next */ 65 | if (! options.remoteName) { 66 | throw new Error('need options.remoteName'); 67 | } 68 | 69 | /* istanbul ignore next */ 70 | PouchDB = db.constructor || options.PouchDB; 71 | 72 | /* istanbul ignore next */ 73 | if (! PouchDB) { 74 | throw new Error('need options.PouchDB'); 75 | } 76 | 77 | PouchDB.adapter('remote', PouchRemoteStream.adapter); 78 | 79 | var ret = new EventEmitter(); 80 | ret.cancel = cancel; 81 | var spec = { 82 | db: db, 83 | options: options, 84 | ret: ret, 85 | canceled: false, 86 | dbSync: undefined, 87 | cancel: cancel, 88 | }; 89 | 90 | syncs.push(spec); 91 | 92 | return ret; 93 | 94 | function cancel() { 95 | if (! spec.canceled && spec.dbSync) { 96 | spec.canceled = true; 97 | spec.dbSync.cancel(); 98 | spec.dbSync.removeAllListeners(); 99 | spec.dbSync = undefined; 100 | } 101 | debug('canceled spec'); 102 | } 103 | } 104 | 105 | function setupSyncs() { 106 | syncs.forEach(startSync); 107 | } 108 | 109 | function startSync(spec) { 110 | debug('startSync: %j', spec.options); 111 | debug('sync.canceled: %j', spec.canceled); 112 | var dbSync; 113 | 114 | /* istanbul ignore else */ 115 | if (!spec.canceled) { 116 | channels.channel({ 117 | database: spec.options.remoteName, 118 | credentials: spec.options.credentials, 119 | }, onChannel); 120 | spec.ret.cancel = cancel; 121 | } 122 | 123 | function onChannel(err, channel) { 124 | if (err) { 125 | spec.ret.emit('error', err); 126 | } else { 127 | var remote = PouchRemoteStream(); 128 | var remoteDB = new PouchDB({ 129 | name: spec.options.remoteName, 130 | adapter: 'remote', 131 | remote: remote, 132 | }); 133 | debug('syncing %j to remote %j', spec.db._db_name, remoteDB._db_name); 134 | dbSync = spec.dbSync = PouchDB.sync(spec.db, remoteDB, {live: true}); 135 | 136 | interestingSyncEvents.forEach(function eachEvent(event) { 137 | dbSync.on(event, function onEvent(payload) { 138 | spec.ret.emit(event, payload); 139 | }); 140 | }); 141 | 142 | channel.on('error', propagateChannelError); 143 | remote.stream.on('error', propagateChannelError); 144 | channel.pipe(remote.stream).pipe(channel); 145 | } 146 | } 147 | 148 | /* istanbul ignore next */ 149 | function propagateChannelError(channelError) { 150 | if (channelError) { 151 | spec.ret.emit('error', channelError); 152 | } 153 | } 154 | 155 | function cancel() { 156 | spec.cancel(); 157 | } 158 | } 159 | 160 | function cancelAll() { 161 | debug('cancelAll'); 162 | syncs.forEach(function eachSync(spec) { 163 | spec.cancel(); 164 | }); 165 | } 166 | 167 | function destroy() { 168 | /* istanbul ignore else */ 169 | cancelAll(); 170 | r.reconnect = false; 171 | r.disconnect(); 172 | } 173 | 174 | function propagateError(err) { 175 | /* istanbul ignore else */ 176 | if (err && err.message !== 'write after end') { 177 | client.emit('error', err); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.createServer = require('./server'); 2 | exports.createClient = require('./client'); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pouch-stream-multi-sync", 3 | "version": "0.3.0", 4 | "description": "Sync several PouchDBs through a stream.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node --harmony node_modules/istanbul/lib/cli.js cover -- lab -vl && istanbul check-coverage", 8 | "style": "eslint ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/pgte/pouch-stream-multi-sync.git" 13 | }, 14 | "author": "pgte", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/pgte/pouch-stream-multi-sync/issues" 18 | }, 19 | "homepage": "https://github.com/pgte/pouch-stream-multi-sync#readme", 20 | "tags": [ 21 | "pouchdb", 22 | "stream", 23 | "sync" 24 | ], 25 | "dependencies": { 26 | "debug": "^2.2.0", 27 | "pipe-channels": "^0.1.1", 28 | "pouch-remote-stream": "^0.3.0", 29 | "pouch-stream-server": "^0.2.1", 30 | "reconnect-core": "^1.1.0", 31 | "xtend": "^4.0.1" 32 | }, 33 | "devDependencies": { 34 | "code": "^2.0.1", 35 | "eslint": "^1.10.1", 36 | "istanbul": "^0.4.0", 37 | "lab": "^7.3.0", 38 | "memdown": "^1.1.0", 39 | "pouchdb": "^5.1.0" 40 | }, 41 | "pre-commit": [ 42 | "style", 43 | "test" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('pouch-stream-multi-sync:server'); 2 | var PipeChannels = require('pipe-channels'); 3 | var PouchStreamServer = require('pouch-stream-server'); 4 | 5 | module.exports = createServer; 6 | 7 | function createServer(onDatabase) { 8 | const channelServer = PipeChannels.createServer(); 9 | const pouchServer = PouchStreamServer(); 10 | 11 | if (typeof onDatabase !== 'function') { 12 | throw new Error('need a request handler as first argument'); 13 | } 14 | 15 | channelServer.on('request', onRequest); 16 | 17 | function onRequest(req) { 18 | var database = req.payload.database; 19 | var credentials = req.payload.credentials; 20 | 21 | debug('going to emit database event, credentials = %j, database = %j', credentials, database); 22 | 23 | onDatabase.call(null, credentials, database, callback); 24 | 25 | function callback(err, db) { 26 | if (err) { 27 | req.deny(err.message || /* istanbul ignore next */ err); 28 | } else { 29 | pouchServer.dbs.add(database, db); 30 | var channel = req.grant(); 31 | channel.on('error', propagateError); 32 | 33 | var pouchStream = pouchServer.stream(); 34 | pouchStream.on('error', propagateError); 35 | 36 | channel.pipe(pouchStream).pipe(channel); 37 | } 38 | } 39 | } 40 | 41 | return channelServer; 42 | 43 | /* istanbul ignore next */ 44 | function propagateError(err) { 45 | if (err && err.message !== 'write after end') { 46 | channelServer.emit('error', err); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/client.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('pouch-stream-multi-sync:test'); 2 | var Lab = require('lab'); 3 | var lab = exports.lab = Lab.script(); 4 | var describe = lab.experiment; 5 | var before = lab.before; 6 | var after = lab.after; 7 | var it = lab.it; 8 | var Code = require('code'); 9 | var expect = Code.expect; 10 | 11 | var PassThrough = require('stream').PassThrough; 12 | var timers = require('timers'); 13 | var PouchDB = require('pouchdb'); 14 | var PouchSync = require('../'); 15 | 16 | describe('pouch-stream-multi-sync', function() { 17 | var connection; 18 | var db = new PouchDB({ 19 | name: 'noneed', 20 | db: require('memdown'), 21 | }); 22 | var sync; 23 | 24 | describe('client', function() { 25 | var client; 26 | it('can be immediately canceled', function(done) { 27 | client = PouchSync.createClient(connectClient); 28 | var sync = client.sync(db, { 29 | credentials: { token: 'some token'}, 30 | remoteName: 'remote name'}); 31 | sync.cancel(); 32 | done(); 33 | }); 34 | 35 | it('can be created again', function(done) { 36 | client = PouchSync.createClient(connectClient); 37 | client.sync(db, { 38 | credentials: { token: 'some token'}, 39 | remoteName: 'remote name'}); 40 | done(); 41 | }); 42 | 43 | it('can propagate stream errors', function(done) { 44 | client.once('error', function(err) { 45 | expect(err).to.be.an.object(); 46 | expect(err.message).to.equal('this should have been caught'); 47 | done(); 48 | }); 49 | timers.setImmediate(function() { 50 | connection.emit('error', new Error('this should have been caught')); 51 | }); 52 | client.connect(); 53 | }); 54 | 55 | it('can be created again', function(done) { 56 | client = PouchSync.createClient(connectClient); 57 | client.sync(db, { 58 | credentials: { token: 'some token'}, 59 | remoteName: 'remote name'}); 60 | done(); 61 | }); 62 | 63 | it('can start sync', function(done) { 64 | sync = client.sync(db, { 65 | credentials: { token: 'some token'}, 66 | remoteName: 'remote name'}); 67 | done(); 68 | }); 69 | 70 | it('can cancel sync', function(done) { 71 | sync.cancel(); 72 | done(); 73 | }); 74 | 75 | it('can be destroyed', function(done) { 76 | client.destroy(); 77 | done(); 78 | }); 79 | 80 | it('waits a bit', function(done) { 81 | setTimeout(done, 1000); 82 | }); 83 | 84 | function connectClient() { 85 | connection = new PassThrough(); 86 | timers.setImmediate(function() { 87 | connection.emit('connect'); 88 | }); 89 | return connection; 90 | } 91 | }); 92 | }); 93 | 94 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('pouch-stream-multi-sync:test'); 2 | var Lab = require('lab'); 3 | var lab = exports.lab = Lab.script(); 4 | var describe = lab.experiment; 5 | var before = lab.before; 6 | var after = lab.after; 7 | var it = lab.it; 8 | var Code = require('code'); 9 | var expect = Code.expect; 10 | 11 | var PouchSync = require('../'); 12 | 13 | describe('pouch-stream-multi-sync', function() { 14 | describe('server', function() { 15 | it('throws if no request handler', function(done) { 16 | expect(function() { 17 | PouchSync.createServer(); 18 | }).to.throw('need a request handler as first argument'); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /test/tandem.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('pouch-stream-multi-sync:test'); 2 | var Lab = require('lab'); 3 | var lab = exports.lab = Lab.script(); 4 | var describe = lab.experiment; 5 | var before = lab.before; 6 | var after = lab.after; 7 | var it = lab.it; 8 | var Code = require('code'); 9 | var expect = Code.expect; 10 | 11 | var PouchDB = require('pouchdb'); 12 | var http = require('http'); 13 | 14 | var PouchSync = require('../'); 15 | 16 | describe('pouch-stream-multi-sync', function() { 17 | var db = new PouchDB({ 18 | name: 'todos', 19 | db: require('memdown'), 20 | }); 21 | var serverDB = new PouchDB({ 22 | name: 'todos-server', 23 | db: require('memdown'), 24 | }); 25 | var server; 26 | var client; 27 | var handler; 28 | 29 | describe('server', function() { 30 | 31 | it('can be created', function(done) { 32 | server = PouchSync.createServer(function(creds, db, cb) { 33 | if (! handler) { 34 | cb(new Error('no database event listener on server')); 35 | } else { 36 | handler.apply(null, arguments); 37 | } 38 | }); 39 | server.setMaxListeners(Infinity); 40 | done(); 41 | }); 42 | 43 | }); 44 | 45 | describe('client', function() { 46 | it('can be created', function(done) { 47 | client = PouchSync.createClient(connectClient); 48 | client.setMax 49 | client.connect(); 50 | done(); 51 | }); 52 | 53 | it('can be made to sync', function(done) { 54 | var sync = client.sync(db, { credentials: { token: 'some token'}}); 55 | sync.once('error', function(err) { 56 | expect(err).to.be.an.object(); 57 | expect(err.message).to.equal('no database event listener on server'); 58 | sync.cancel(); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | 64 | describe('server', function() { 65 | it('can deny database requests', function(done) { 66 | handler = function(credentials, database, callback) { 67 | expect(credentials).to.deep.equal({token: 'some token'}); 68 | callback(new Error('go away')); 69 | }; 70 | 71 | client = PouchSync.createClient(connectClient); 72 | client.connect(); 73 | var sync = client.sync(db, { credentials: { token: 'some token'}}); 74 | 75 | sync.once('error', function(err) { 76 | expect(err).to.be.an.object(); 77 | expect(err.message).to.equal('go away'); 78 | sync.cancel(); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('can accept database requests', function(done) { 84 | handler = function(credentials, database, callback) { 85 | expect(credentials).to.deep.equal({token: 'some other token'}); 86 | callback(null, serverDB); 87 | }; 88 | 89 | client = PouchSync.createClient(connectClient); 90 | client.connect(); 91 | var sync = client.sync(db, { 92 | credentials: { token: 'some other token'}, 93 | remoteName: 'todos-server', 94 | }); 95 | 96 | db.put({_id: 'A', a:1, b: 2}, function(err, reply) { 97 | if (err) throw err; 98 | sync.once('change', function() { 99 | serverDB.get('A', function(err, doc) { 100 | expect(err).to.equal(null); 101 | expect(doc).to.deep.equal({ 102 | _id: 'A', 103 | a: 1, 104 | b: 2, 105 | _rev: reply.rev, 106 | }); 107 | sync.cancel(); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | 113 | }); 114 | }); 115 | 116 | function connectClient() { 117 | setTimeout(function() { 118 | server.emit('connect'); 119 | }, 100); 120 | return server; 121 | } 122 | }); 123 | --------------------------------------------------------------------------------