├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.js ├── index.js ├── lib ├── connect-couchdb.js └── connect-session.json ├── package.json ├── test └── test.store.js └── tools ├── cleanup.tests.js ├── put_database.js ├── put_design_docs.js ├── put_options.js └── setup.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *.iml 3 | test/credentials.js 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | 6 | services: 7 | - couchdb 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Thomas Debarochez. All rights reserved. 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to 4 | deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 18 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect CouchDB 2 | 3 | `connect-couchdb` is a storage wrapper for the [express-session](https://github.com/expressjs/session) middleware of [connect](https://github.com/senchalabs/connect)/ 4 | [expressjs](https://github.com/visionmedia/express) frameworks. 5 | [![Build Status](https://secure.travis-ci.org/tdebarochez/connect-couchdb.png)](http://travis-ci.org/tdebarochez/connect-couchdb) 6 | 7 | ## Requirements 8 | 9 | - couchdb (tested with v1.5.0) 10 | - [yacw](https://github.com/tdebarochez/yacw) 0.2.x : the couchdb wrapper. Should be easy to use another one. 11 | - [mocha](https://github.com/visionmedia/mocha) (only for tests) 12 | 13 | ## Compatibility 14 | 15 | For node v0.10, Connect v3 and Express v4 you must use the master branch. 16 | For olders node version, use others branches. 17 | 18 | ## Installation 19 | 20 | Via npm: 21 | 22 | $ npm install connect-couchdb --save 23 | 24 | ## Usage 25 | 26 | var session = require('express-session'), 27 | connect = require('connect'); 28 | ConnectCouchDB = require('connect-couchdb')(session); 29 | 30 | var store = new ConnectCouchDB({ 31 | // Name of the database you would like to use for sessions. 32 | name: 'myapp-sessions', 33 | 34 | // Optional. Database connection details. See yacw documentation 35 | // for more informations 36 | username: 'username', 37 | password: 'password', 38 | host: 'localhost', 39 | 40 | // Optional. How often expired sessions should be cleaned up. 41 | // Defaults to 600000 (10 minutes). 42 | reapInterval: 600000, 43 | 44 | // Optional. How often to run DB compaction against the session 45 | // database. Defaults to 300000 (5 minutes). 46 | // To disable compaction, set compactInterval to -1 47 | compactInterval: 300000, 48 | 49 | // Optional. How many time between two identical session store 50 | // Defaults to 60000 (1 minute) 51 | setThrottle: 60000 52 | }); 53 | var server = connect(); 54 | server.use(session({secret: 'YourSecretKey', store: store }); 55 | 56 | If the database specified doesn't already exist you have to create it with 57 | `tools/` files. Run following command to create database, populate with the 58 | design document and setup the CouchDB database specific option `_revs_limit` : 59 | 60 | $ node tools/setup.js [username] [password] 61 | 62 | For more informations about the `_revs_limit` option, read 63 | [this](http://wiki.apache.org/couchdb/HTTP_database_API#Accessing_Database-specific_options). 64 | 65 | It is highly recommended that you use a separate database for your 66 | sessions for performance of both the session views and any other document 67 | views you may have. 68 | 69 | See `example.js` file for an example connect server using `connect-couch`. 70 | 71 | ## Updating 72 | 73 | Please invoke the tool to create the design documents when updating to insure you are using the last version of the view. 74 | 75 | $ node tools/put_design_docs.js [username] [password] 76 | 77 | ## Tests 78 | 79 | $ npm test 80 | 81 | ## Author 82 | 83 | - Thomas de Barochez ([tdebarochez](https://github.com/tdebarochez)) 84 | 85 | ## Contributors 86 | 87 | $ git shortlog -s -n 88 | 89 | - Ian Ward ([ianshward](https://github.com/ianshward)) 90 | - Young Hahn ([yhahn](https://github.com/yhahn)) 91 | - Ryan Kirkman ([ryankirkman](https://github.com/ryankirkman)) 92 | - Andreas Lappe ([alappe](https://github.com/alappe)) 93 | - Cliffano Subagio ([cliffano](https://github.com/cliffano)) 94 | - Dan VerWeire ([wankdanker](https://github.com/wankdanker)) 95 | - Daniel Bell ([danbell](https://github.com/danbell)) 96 | - Konstantin Käfer ([kkaefer](https://github.com/kkaefer)) 97 | - Pau Ramon Revilla ([masylum](https://github.com/masylum)) 98 | - Quentin Raynaud ([even](https://github.com/even)) 99 | - Ivan Erceg ([ierceg](https://github.com/ierceg)) 100 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var session = require('express-session'), 2 | express = require('express'), 3 | connectCouchDB = require('./lib/connect-couchdb')(session); 4 | 5 | function helloWorld(req, res, next) { 6 | if (req.url !== '/') return next(); 7 | 8 | req.session.tick = req.session.tick || 0; 9 | req.session.tick++; 10 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 11 | res.write('hello world : ' + req.session.tick); 12 | res.end(''); 13 | } 14 | 15 | var app = express(); 16 | app.use(session({ 17 | secret: 'your secret passphrase', 18 | store: new connectCouchDB({name: "test"}), 19 | saveUninitialized:true, 20 | resave:false 21 | })); 22 | app.use(helloWorld); 23 | app.listen(3000); 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/connect-couchdb'); -------------------------------------------------------------------------------- /lib/connect-couchdb.js: -------------------------------------------------------------------------------- 1 | var Couch = require('yacw'); 2 | 3 | module.exports = function(session) { 4 | var Store = session.Store; 5 | 6 | function ConnectCouchDB(opts, fn) { 7 | opts = opts || {}; 8 | if (fn) { 9 | console.error('DEPRECATED usage of callback function in ConnectCouchDB\'s constructor'); 10 | } 11 | 12 | Store.call(this, opts); 13 | 14 | this.db = null; 15 | this.reapInterval = opts.reapInterval || (10 * 60 * 1000); 16 | this.compactInterval = opts.compactInterval || (5 * 60 * 1000); 17 | this.setThrottle = opts.setThrottle || 60000; 18 | 19 | // Even when _revs_limit is set to 1, old revisions take up space, 20 | // therefore, compact the database every once in awhile. 21 | if (this.reapInterval > 0) 22 | this._reap = setInterval(this.reap.bind(this), this.reapInterval); 23 | if (this.compactInterval > 0) 24 | this._compact = setInterval(this.compact.bind(this), this.compactInterval); 25 | 26 | if (!opts.name && !opts.uri) { 27 | throw "You must define a database"; 28 | } 29 | this.db = new Couch(opts); 30 | } 31 | 32 | function _check_error(err) { 33 | if (err !== null) { 34 | console.log("connect-couchdb error:"); 35 | console.log(err); 36 | console.log(new Error().stack); 37 | } 38 | } 39 | 40 | function _uri_encode(id) { 41 | // starting storage key with undescore is reserved. See issue #24. 42 | var prefix = 'connect-session_'; 43 | // We first decode it to escape any current URI encoding. 44 | return encodeURIComponent(decodeURIComponent(id.substr(0, prefix.length) === prefix ? '' : prefix) + id); 45 | } 46 | 47 | ConnectCouchDB.prototype.__proto__ = Store.prototype; 48 | 49 | ConnectCouchDB.prototype.setupOptions = function (opts, fn) { 50 | if ('revs_limit' in opts) { 51 | this.db.putOpt('_revs_limit', opts.revs_limit, function(err) { 52 | _check_error(err); 53 | fn && fn(err); 54 | }); 55 | } 56 | else { 57 | fn && fn(null); 58 | } 59 | } 60 | 61 | ConnectCouchDB.prototype.setupDesignDocs = function (fn) { 62 | var self = this; 63 | 64 | function createDesignDocs() { 65 | // Create the design document 66 | self.db.putDesignDocs([__dirname + '/connect-session.json'], function(err) { 67 | _check_error(err); 68 | fn && fn(err); 69 | }); 70 | } 71 | 72 | // Try to get the design doc if it exists 73 | this.db.get('_design/connect-sessions', function(err, doc) { 74 | // Delete the design docs if it exists & create the new one 75 | if (!err) 76 | self.db.del(doc, createDesignDocs); 77 | else 78 | createDesignDocs(); 79 | }); 80 | } 81 | 82 | ConnectCouchDB.prototype.setupDatabase = function (fn) { 83 | this.db.get('', function(err, doc) { 84 | if (err && err.statusCode === 404) { 85 | this.db.dbPut(function(err) { 86 | _check_error(err); 87 | fn && fn(err); 88 | }.bind(this)); 89 | } 90 | else { 91 | _check_error(err); 92 | fn && fn(err); 93 | } 94 | }.bind(this)); 95 | }; 96 | 97 | ConnectCouchDB.prototype.setup = function (opts, fn) { 98 | this.setupDatabase(function (err) { 99 | if (err) return fn && fn(err); 100 | this.setupDesignDocs(function (err) { 101 | if (err) return fn && fn(err); 102 | this.setupOptions(opts, function (err) { 103 | fn && fn(err); 104 | }); 105 | }.bind(this)); 106 | }.bind(this)); 107 | }; 108 | 109 | ConnectCouchDB.prototype.get = function (sid, fn) { 110 | sid = _uri_encode(sid); 111 | var now = +new Date; 112 | this.db.get(sid, function (err, doc) { 113 | if (err) { 114 | if (err.error == "not_found") err.code = "ENOENT"; 115 | return fn && fn(err); 116 | } else if (doc.expires && now >= doc.expires) { 117 | return fn && fn(null, null); 118 | } else { 119 | return fn && fn(null, doc.sess); 120 | } 121 | }.bind(this)); 122 | }; 123 | 124 | ConnectCouchDB.prototype.set = function (sid, sess, fn) { 125 | sid = _uri_encode(sid); 126 | fn = fn || function () {}; 127 | this.db.get(sid, function (err, doc) { 128 | var expires = typeof sess.cookie.maxAge === 'number' 129 | ? (+new Date()) + sess.cookie.maxAge 130 | : (+new Date()) + (24 * 60 * 60 * 1000); 131 | if (err) { 132 | doc = { 133 | _id: sid, 134 | expires: expires, 135 | type: 'connect-session', 136 | sess: sess 137 | }; 138 | this.db.put(doc, fn); 139 | } else { 140 | var accessGap = sess.lastAccess - doc.sess.lastAccess; 141 | // Temporarily remove properties for session comparison 142 | var _lastAccess = sess.lastAccess; 143 | var _originalMaxAge = sess.cookie.originalMaxAge; 144 | if (sess.cookie._expires) { 145 | var _expires = sess.cookie._expires; 146 | sess.cookie._expires = null; 147 | } 148 | if (doc.sess.cookie.expires) { 149 | doc.sess.cookie.expires = null; 150 | } 151 | sess.lastAccess = null; 152 | sess.cookie.originalMaxAge = null; 153 | doc.sess.lastAccess = null; 154 | doc.sess.cookie.originalMaxAge = null; 155 | // Compare new session to current session, save if different 156 | // or setThrottle elapses 157 | if (JSON.stringify(doc.sess) !== JSON.stringify(sess) 158 | || accessGap > this.setThrottle) { 159 | sess.lastAccess = _lastAccess; 160 | if (_expires) sess.cookie._expires = _expires; 161 | sess.cookie.originalMaxAge = _originalMaxAge; 162 | doc.expires = expires; 163 | doc.sess = sess; 164 | this.db.put(doc, fn); 165 | } else { 166 | return fn(); 167 | } 168 | } 169 | }.bind(this)); 170 | }; 171 | 172 | ConnectCouchDB.prototype.destroy = function (sid, fn) { 173 | sid = _uri_encode(sid); 174 | this.db.get(sid, function (err, doc) { 175 | if (err) return fn && fn(err); 176 | this.db.del(doc, fn); 177 | }.bind(this)); 178 | }; 179 | 180 | function destroy(docs, fn) { 181 | var self = this; 182 | var deleted_docs = [] 183 | docs.forEach(function(doc) { 184 | deleted_docs.push({_id: doc.doc._id, _rev: doc.doc._rev, _deleted: true}); 185 | }); 186 | this.db.bulk({docs: deleted_docs}, fn); 187 | } 188 | 189 | ConnectCouchDB.prototype.clear = function (fn) { 190 | var options = { reduce: false, include_docs: true }; 191 | this.db.view('_design/connect-sessions/_view/expires', options, function (err, docs) { 192 | if (err) return fn && fn(err); 193 | destroy.call(this, docs.rows, fn); 194 | }.bind(this)); 195 | }; 196 | 197 | ConnectCouchDB.prototype.reap = function (fn) { 198 | var now = +new Date; 199 | var options = { endkey: now, reduce: false, include_docs: true }; 200 | this.db.view('_design/connect-sessions/_view/expires', options, function (err, docs) { 201 | if (err) return fn && fn(err); 202 | destroy.call(this, docs.rows, fn); 203 | }.bind(this)); 204 | }; 205 | 206 | ConnectCouchDB.prototype.length = function (fn) { 207 | var now = +new Date; 208 | var options = { startkey: now, reduce: false, include_docs: true }; 209 | this.db.view('_design/connect-sessions/_view/expires', options, function (err, docs) { 210 | if (err) { 211 | return fn && fn(err); 212 | } else { 213 | return fn && fn(err, docs.total_rows - docs.offset); 214 | } 215 | }); 216 | }; 217 | 218 | ConnectCouchDB.prototype.compact = function () { 219 | var obj = {endpoint: '_compact', doc: {}}; 220 | this.db.post(obj, function(err, res) {}); 221 | }; 222 | 223 | ConnectCouchDB.prototype.clearInterval = function () { 224 | if (this._reap) clearInterval(this._reap); 225 | if (this._compact) clearInterval(this._compact); 226 | }; 227 | 228 | return ConnectCouchDB; 229 | } 230 | -------------------------------------------------------------------------------- /lib/connect-session.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "_design/connect-sessions", 3 | "language":"javascript", 4 | "views": { 5 | "expires": { 6 | "map": "(function (doc) {\n if (doc.type == 'connect-session' && doc.expires) {\n emit(doc.expires);\n }\n })" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-couchdb", 3 | "description": "CouchDB session store for Connect", 4 | "keywords" : [ "connect", "session", "couchdb", "middleware" ], 5 | "version": "1.0.0", 6 | "node": ">= 0.10.0", 7 | "author": "Thomas de Barochez ", 8 | "contributors": ["Ian Ward (https://github.com/ianshward)", 9 | "Young Hahn (https://github.com/yhahn)", 10 | "Ryan Kirkman (https://github.com/ryankirkman)", 11 | "Dan VerWeire (https://github.com/wankdanker)", 12 | "Pau Ramon Revilla (https://github.com/masylum)", 13 | "Konstantin Käfer (https://github.com/kkaefer)", 14 | "Daniel Bell (https://github.com/danbell)", 15 | "Quentin Raynaud (https://github.com/even)", 16 | "Ivan Erceg (https://github.com/ierceg"], 17 | "main": "./index.js", 18 | "dependencies": { 19 | "connect": "3.*", 20 | "yacw": "0.2.x", 21 | "express-session": "^1.6.1" 22 | }, 23 | "devDependencies": { 24 | "mocha": "*" 25 | }, 26 | "scripts": { 27 | "test": "node tools/cleanup.tests.js ; mocha" 28 | }, 29 | "engines": { "node": ">= 0.10.0" }, 30 | "repository" : {"type" : "git", 31 | "url" : "http://github.com/tdebarochez/connect-couchdb.git"} 32 | } 33 | -------------------------------------------------------------------------------- /test/test.store.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , session = require('express-session') 3 | , path = require('path') 4 | , fs = require('fs') 5 | , ConnectCouchDB = require('../')(session) 6 | , global_opts = {"name": 'connect-couchdb-' + +new Date}; 7 | 8 | if (fs.existsSync('./test/credentials.json')) { 9 | var credentials = require('./credentials.json'); 10 | global_opts.username = credentials.username; 11 | global_opts.password = credentials.password; 12 | } 13 | 14 | function reason (err) { 15 | return !err ? '' : err.reason; 16 | } 17 | 18 | describe('connect-session.json', function () { 19 | it('is a valid json', function(done) { 20 | assert.doesNotThrow(function() { 21 | fs.readFile('lib/connect-session.json', function(err, data) { 22 | assert.ok(!err, reason(err)); 23 | JSON.parse(data.toString()); 24 | done(); 25 | }); 26 | }); 27 | }); 28 | }); 29 | describe('db', function () { 30 | it('put only if needed', function (done) { 31 | var opts = global_opts; 32 | opts.name = 'connect-couch-puttest'; 33 | var store = new ConnectCouchDB(opts); 34 | var cookie = { cookie: { maxAge: 2000 }, name: 'nd' }; 35 | store.setup(opts, function(err, res) { 36 | assert.ok(!err, reason(err)); 37 | store.set('987', cookie, function(err, ok) { 38 | assert.ok(!err, reason(err)); 39 | // Redefine store.db.put to assure that it's not executed any more: 40 | store.db._put = store.db.put; 41 | store.db.put = function(doc, fn) { 42 | throw new Error('This put is not needed!'); 43 | }; 44 | store.set('987', cookie, function(err, ok) { 45 | assert.ok(!err, reason(err)); 46 | store.destroy('987', function() { 47 | store.length(function(err, len){ 48 | assert.equal(0, len, '#set() null'); 49 | store.clearInterval(); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | }); 55 | }); 56 | }); 57 | // Test basic set/get/clear/length functionality. 58 | it('set/get/clear/length', function (done) { 59 | var opts = global_opts; 60 | var c = { cookie: { maxAge: 2000 }, name: 'tj' }; 61 | opts.name = 'connect-couch-test'; 62 | opts.revs_limit = '2'; 63 | var store = new ConnectCouchDB(opts); 64 | store.setup(opts, function (err, res) { 65 | assert.ok(!err, reason(err)); 66 | store.db.getOpt('_revs_limit', function (err, res) { 67 | assert.ok(!err, reason(err)); 68 | assert.equal(res, opts.revs_limit); 69 | // #set() 70 | store.set('123', c, function(err, ok){ 71 | assert.ok(!err, '#set() got an error'); 72 | // #get() 73 | store.get('123', function(err, data){ 74 | assert.ok(!err, '#get() got an error : ' + reason(err)); 75 | assert.deepEqual(c, data); 76 | // #length() 77 | store.length(function(err, len){ 78 | assert.ok(!err, '#length() got an error : ' + reason(err)); 79 | assert.equal(1, len, '#length() with keys : ' + reason(err)); 80 | // #clear() 81 | store.clear(function(err, ok){ 82 | assert.ok(!err, '#clear() : ' + reason(err)); 83 | // #length() 84 | store.length(function(err, len){ 85 | assert.ok(!err, reason(err)); 86 | assert.equal(0, len, '#length(' + len + ') without keys'); 87 | // #set null 88 | store.set('123', c, function(){ 89 | store.destroy('123', function(){ 90 | store.length(function(err, len){ 91 | assert.ok(!err, reason(err)); 92 | assert.equal(0, len, '#set() null'); 93 | store.clearInterval(); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | }); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | // Test expired session reaping. 107 | it('reaping', function (done) { 108 | var opts = global_opts; 109 | opts.name = 'connect-couch-reap'; 110 | opts.reapInterval = 500; 111 | var store = new ConnectCouchDB(opts); 112 | store.setupDatabase(function (err, res) { 113 | assert.ok(!err, reason(err)); 114 | store.setupDesignDocs(function (err, res) { 115 | assert.ok(!err, reason(err)); 116 | store.setupOptions(opts, function(err, res) { 117 | assert.ok(!err, reason(err)); 118 | var cb = function (i) { 119 | return function (err) { 120 | assert.ok(!err, 'error with #' + i + ' : ' + reason(err)); 121 | }; 122 | }; 123 | store.set('1', { cookie: { maxAge: 250 } }, cb(1)); 124 | store.set('2', { cookie: { maxAge: 250 } }, cb(2)); 125 | store.set('3', { cookie: { maxAge: 5000 } }, cb(3)); 126 | store.set('4', { cookie: { maxAge: 5000 } }, cb(4)); 127 | setTimeout(function() { 128 | store.length(function(err, len) { 129 | assert.ok(!err, reason(err)); 130 | assert.equal(2, len, '#length(' + len + ') after reap'); 131 | store.clearInterval(); 132 | done(); 133 | }); 134 | }, 1000); 135 | }); 136 | }); 137 | }); 138 | }); 139 | // Test session put throttling 140 | it('throttling', function (done) { 141 | var opts = global_opts; 142 | opts.name = 'connect-couch-throttle'; 143 | opts.setThrottle = 1000; 144 | opts.revs_limit = '4'; 145 | var store = new ConnectCouchDB(opts); 146 | store.setup(opts, function (err, res) { 147 | assert.ok(!err, reason(err)); 148 | // Set new session 149 | store.set('123', { cookie: { 150 | maxAge: 20000, originalMaxAge: 20000 }, 151 | name: 'foo', 152 | lastAccess: 13253760000000 153 | }, function(err, ok){ 154 | assert.ok(!err, reason(err)); 155 | // Set again, now added to locks object in connect-couchdb.js 156 | store.set('123', { cookie: { 157 | maxAge: 20000, originalMaxAge: 19999 }, 158 | name: 'foo', 159 | lastAccess: 13253760000001 160 | }, function(err, ok){ 161 | assert.ok(!err, reason(err)); 162 | var start = new Date().getTime(); 163 | store.get('123', function(err, data){ 164 | var orig = data; 165 | // If we set again now, and less than 1s passes, session should not change 166 | store.set('123', { cookie: { 167 | maxAge: 20000, originalMaxAge: 19998 }, 168 | name: 'foo', 169 | lastAccess: 13253760000002 170 | }, function(err, ok){ 171 | assert.ok(!err, reason(err)); 172 | store.get('123', function(err, data){ 173 | var stop = new Date().getTime(); 174 | if (stop - start < 1000) { 175 | assert.equal(JSON.stringify(orig), JSON.stringify(data), 176 | 'Sub-microsecond session update without data change should be equal' 177 | ); 178 | } else { 179 | assert.equal(false, JSON.stringify(orig) === JSON.stringify(data), 180 | '> 1s session update without data change should not be equal' 181 | ); 182 | } 183 | // Now delay a second and the session time-related data should change 184 | var orig = data; 185 | var start = new Date().getTime(); 186 | setTimeout(function() { 187 | store.set('123', { cookie: { 188 | maxAge: 20000, originalMaxAge: 19997 }, 189 | name: 'foo', 190 | lastAccess: 13253760001003 191 | }, function(err, ok){ 192 | assert.ok(!err, reason(err)); 193 | store.get('123', function(err, data){ 194 | var stop = new Date().getTime(); 195 | // session data not changed. If two sets occurred < 1s, objects should be identical 196 | if (stop - start < 1000) { 197 | assert.equal(JSON.stringify(orig), JSON.stringify(data), 198 | 'Sub-microsecond session update without data change should be equal' 199 | ); 200 | } else { 201 | assert.equal(false, JSON.stringify(orig) === JSON.stringify(data), 202 | '> 1s session update without data change should not be equal' 203 | ); 204 | } 205 | // Now make change to data, session should change no matter what. 206 | store.set('123', { cookie: { 207 | maxAge: 20000, _expires: 13253760000003, originalMaxAge: 19997 }, 208 | name: 'bar', 209 | lastAccess: 13253760001003 210 | }, function(err, ok){ 211 | store.get('123', function(err, data){ 212 | assert.equal(false, JSON.stringify(orig) === JSON.stringify(data), 213 | 'Sub-microsecond session update without data change should be equal' 214 | ); 215 | store.clearInterval(); 216 | done(); 217 | }); 218 | }); 219 | }); 220 | }); 221 | }, opts.setThrottle + 100); 222 | }); 223 | }); 224 | }); 225 | }); 226 | }); 227 | }); 228 | }); 229 | it("id leading with underscore", function (done) { 230 | var opts = global_opts; 231 | opts.name = 'connect-couch-underscoretest'; 232 | var store = new ConnectCouchDB(opts); 233 | var cookie = { cookie: { maxAge: 2000 }, name: 'nd' }; 234 | store.setup(opts, function(err, res) { 235 | assert.ok(!err, reason(err)); 236 | store.set('_12345', cookie, function(err, ok) { 237 | assert.ok(!err, reason(err)); 238 | store.get('_12345', function(err, ok) { 239 | assert.ok(!err, reason(err)); 240 | store.clearInterval(); 241 | done(); 242 | }); 243 | }); 244 | }); 245 | }); 246 | }); 247 | -------------------------------------------------------------------------------- /tools/cleanup.tests.js: -------------------------------------------------------------------------------- 1 | var Couch = require('yacw'); 2 | 3 | var databases = ['connect-couch-underscoretest', 4 | 'connect-couch-throttle', 5 | 'connect-couch-reap', 6 | 'connect-couch-test', 7 | 'connect-couch-puttest']; 8 | databases.forEach(function (database_name) { 9 | (new Couch({name: database_name})).dbDel(); 10 | }); -------------------------------------------------------------------------------- /tools/put_database.js: -------------------------------------------------------------------------------- 1 | var session = require('express-session'); 2 | 3 | if (process.argv.length < 3) { 4 | console.error('Usage : $ node put_database.js [username] [password]'); 5 | process.exit(1); 6 | } 7 | 8 | var opts = {"name": process.argv[2]}; 9 | if (process.argv.length >= 4) { 10 | opts.username = process.argv[3]; 11 | } 12 | if (process.argv.length >= 5) { 13 | opts.password = process.argv[4]; 14 | } 15 | 16 | var connect_couchdb = new (require(__dirname + '/../lib/connect-couchdb.js')(session))(opts); 17 | 18 | connect_couchdb.setupDatabase(function (err) { 19 | if (err) { 20 | console.error(err); 21 | return; 22 | } 23 | console.log('ok !'); 24 | process.exit(0); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /tools/put_design_docs.js: -------------------------------------------------------------------------------- 1 | var session = require('express-session'); 2 | 3 | if (process.argv.length < 3) { 4 | console.error('Usage : $ node put_design_docs.js [username] [password]'); 5 | process.exit(1); 6 | } 7 | 8 | var opts = {"name": process.argv[2]}; 9 | if (process.argv.length >= 4) { 10 | opts.username = process.argv[3]; 11 | } 12 | if (process.argv.length >= 5) { 13 | opts.password = process.argv[4]; 14 | } 15 | 16 | var connect_couchdb = new (require(__dirname + '/../lib/connect-couchdb.js')(session))(opts); 17 | 18 | connect_couchdb.setupDesignDocs(function (err) { 19 | if (err) { 20 | console.error(err); 21 | return; 22 | } 23 | console.log('ok !'); 24 | process.exit(0); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /tools/put_options.js: -------------------------------------------------------------------------------- 1 | var session = require('express-session'); 2 | 3 | if (process.argv.length < 3) { 4 | console.error('Usage : $ node put_options.js [username] [password]'); 5 | process.exit(1); 6 | } 7 | 8 | var opts = {"name": process.argv[2], 9 | "revs_limit": process.argv[3]}; 10 | if (process.argv.length >= 5) { 11 | opts.username = process.argv[4]; 12 | } 13 | if (process.argv.length >= 6) { 14 | opts.password = process.argv[5]; 15 | } 16 | 17 | var connect_couchdb = new (require(__dirname + '/../lib/connect-couchdb.js')(session))(opts); 18 | 19 | connect_couchdb.setupOptions(opts, function (err) { 20 | if (err) { 21 | console.error(err); 22 | return; 23 | } 24 | console.log('ok !'); 25 | process.exit(0); 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /tools/setup.js: -------------------------------------------------------------------------------- 1 | var session = require('express-session'); 2 | 3 | if (process.argv.length < 3) { 4 | console.error('Usage : $ node setup.js [username] [password]'); 5 | process.exit(1); 6 | } 7 | 8 | var opts = {"name": process.argv[2], 9 | "revs_limit": process.argv[3]}; 10 | if (process.argv.length >= 5) { 11 | opts.username = process.argv[4]; 12 | } 13 | if (process.argv.length >= 6) { 14 | opts.password = process.argv[5]; 15 | } 16 | 17 | var connect_couchdb = new (require(__dirname + '/../lib/connect-couchdb.js')(session))(opts); 18 | 19 | connect_couchdb.setup(opts, function (err) { 20 | if (err) { 21 | console.error(err); 22 | return; 23 | } 24 | console.log('ok !'); 25 | process.exit(0); 26 | }); 27 | 28 | 29 | --------------------------------------------------------------------------------