(#3444 #3921) - use custom clone, fix Dates
diff --git a/lib/adapter.js b/lib/adapter.js
index a40b86e..be5a109 100644
--- a/lib/adapter.js
+++ b/lib/adapter.js
@@ -106,7 +106,7 @@
     offset: opts.skip
   };
   return Promise.all(keys.map(function (key) {
-    var subOpts = utils.extend(true, {key: key, deleted: 'ok'}, opts);
+    var subOpts = utils.extend({key: key, deleted: 'ok'}, opts);
     ['limit', 'skip', 'keys'].forEach(function (optKey) {
       delete subOpts[optKey];
     });
@@ -722,7 +722,7 @@
     opts = {};
   }
 
-  opts = utils.clone(opts);
+  opts = utils.clone(opts || {});
 
   if (Array.isArray(req)) {
     req = {
diff --git a/lib/adapters/http/index.js b/lib/adapters/http/index.js
index 6fda834..a312847 100644
--- a/lib/adapters/http/index.js
+++ b/lib/adapters/http/index.js
@@ -142,7 +142,7 @@
   var ajaxOpts = opts.ajax || {};
   opts = clone(opts);
   function ajax(options, callback) {
-    var reqOpts = utils.extend(true, clone(ajaxOpts), options);
+    var reqOpts = utils.extend(clone(ajaxOpts), options);
     log(reqOpts.method + ' ' + reqOpts.url);
     return utils.ajax(reqOpts, callback);
   }
@@ -351,7 +351,7 @@
       url: genDBUrl(host, id + params)
     };
     var getRequestAjaxOpts = opts.ajax || {};
-    utils.extend(true, options, getRequestAjaxOpts);
+    utils.extend(options, getRequestAjaxOpts);
 
     function fetchAttachments(doc) {
       var atts = doc._attachments;
diff --git a/lib/adapters/idb/index.js b/lib/adapters/idb/index.js
index b692149..dc78024 100644
--- a/lib/adapters/idb/index.js
+++ b/lib/adapters/idb/index.js
@@ -292,11 +292,8 @@
     var doc;
     var metadata;
     var err;
-    var txn;
-    opts = utils.clone(opts);
-    if (opts.ctx) {
-      txn = opts.ctx;
-    } else {
+    var txn = opts.ctx;
+    if (!txn) {
       var txnResult = openTransactionSafely(idb,
         [DOC_STORE, BY_SEQ_STORE, ATTACH_STORE], 'readonly');
       if (txnResult.error) {
@@ -345,7 +342,6 @@
 
   api._getAttachment = function (attachment, opts, callback) {
     var txn;
-    opts = utils.clone(opts);
     if (opts.ctx) {
       txn = opts.ctx;
     } else {
diff --git a/lib/adapters/websql/index.js b/lib/adapters/websql/index.js
index 6eb0328..812f2cb 100644
--- a/lib/adapters/websql/index.js
+++ b/lib/adapters/websql/index.js
@@ -540,18 +540,15 @@
   };
 
   api._get = function (id, opts, callback) {
-    opts = utils.clone(opts);
     var doc;
     var metadata;
     var err;
-    if (!opts.ctx) {
-      db.readTransaction(function (txn) {
-        opts.ctx = txn;
-        api._get(id, opts, callback);
-      });
-      return;
-    }
     var tx = opts.ctx;
+    if (!tx) {
+      return db.readTransaction(function (txn) {
+        api._get(id, utils.extend({ctx: txn}, opts), callback);
+      });
+    }
 
     function finish() {
       callback(err, {doc: doc, metadata: metadata, ctx: tx});
diff --git a/lib/deps/ajax/ajax-core.js b/lib/deps/ajax/ajax-core.js
index 1f9151d..5640f29 100644
--- a/lib/deps/ajax/ajax-core.js
+++ b/lib/deps/ajax/ajax-core.js
@@ -20,7 +20,7 @@
     cache: false
   };
 
-  options = utils.extend(true, defaultOptions, options);
+  options = utils.extend(defaultOptions, options);
 
 
   function onSuccess(obj, resp, cb) {
diff --git a/lib/deps/clone.js b/lib/deps/clone.js
new file mode 100644
index 0000000..1f9e7ba
--- /dev/null
+++ b/lib/deps/clone.js
@@ -0,0 +1,55 @@
+'use strict';
+
+function isPlainObject(object) {
+  // dead-simple "is this a straight-up object" test, taken
+  // from pouchdb-extend ala jQuery 1.9.0
+  // Own properties are enumerated firstly, so to speed up,
+  // if last one is own, then all properties are own.
+
+  if (typeof object.hasOwnProperty !== 'function') {
+    return false;
+  }
+
+  var key;
+  for (key in object) {}
+  return key === undefined || object.hasOwnProperty(key);
+}
+
+module.exports = function clone(object) {
+  var newObject;
+  var i;
+  var len;
+
+  if (!object || typeof object !== 'object') {
+    return object;
+  }
+
+  if (Array.isArray(object)) {
+    newObject = [];
+    for (i = 0, len = object.length; i < len; i++) {
+      newObject[i] = clone(object[i]);
+    }
+    return newObject;
+  }
+
+  // special case: to avoid inconsistencies between IndexedDB
+  // and other backends, we automatically stringify Dates
+  if (object instanceof Date) {
+    return object.toISOString();
+  }
+
+  if (!isPlainObject(object)) {
+    return object;
+  }
+
+  newObject = {};
+  for (i in object) {
+    if (object.hasOwnProperty(i)) {
+      var value = clone(object[i]);
+      if (typeof value !== 'undefined') {
+        newObject[i] = value;
+      }
+    }
+  }
+  return newObject;
+};
\ No newline at end of file
diff --git a/lib/deps/extend.js b/lib/deps/extend.js
new file mode 100644
index 0000000..a192a47
--- /dev/null
+++ b/lib/deps/extend.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var clone = require('./clone');
+
+function extendInner(obj, otherObj) {
+  for (var key in otherObj) {
+    if (otherObj.hasOwnProperty(key)) {
+      var value = clone(otherObj[key]);
+      if (typeof value !== 'undefined') {
+        obj[key] = value;
+      }
+    }
+  }
+}
+
+module.exports = function extend(obj, obj2, obj3) {
+  extendInner(obj, obj2);
+  if (obj3) {
+    extendInner(obj, obj3);
+  }
+  return obj;
+};
\ No newline at end of file
diff --git a/lib/mapreduce/create-view.js b/lib/mapreduce/create-view.js
index d160632..e5f6983 100644
--- a/lib/mapreduce/create-view.js
+++ b/lib/mapreduce/create-view.js
@@ -1,8 +1,8 @@
 'use strict';
 
 var upsert = require('./upsert');
-var utils = require('./utils');
-var Promise = utils.Promise;
+var Promise = require('../deps/promise');
+var md5 = require('./md5');
 
 module.exports = function (opts) {
   var sourceDB = opts.db;
@@ -25,7 +25,7 @@
   return sourceDB.info().then(function (info) {
 
     var depDbName = info.db_name + '-mrview-' +
-      (temporary ? 'temp' : utils.MD5(viewSignature));
+      (temporary ? 'temp' : md5(viewSignature));
 
     // save the view name in the source db so it can be cleaned up if necessary
     // (e.g. when the _design doc is deleted, remove all associated view data)
diff --git a/lib/mapreduce/index.js b/lib/mapreduce/index.js
index 2ed7484..cf2486a 100644
--- a/lib/mapreduce/index.js
+++ b/lib/mapreduce/index.js
@@ -17,7 +17,8 @@
   log = function () {};
 }
 var utils = require('./utils');
-var Promise = utils.Promise;
+var Promise = require('../deps/promise');
+var inherits = require('inherits');
 var persistentQueues = {};
 var tempViewQueue = new TaskQueue();
 var CHANGES_BATCH_SIZE = 50;
@@ -850,7 +851,7 @@
     callback = opts;
     opts = {};
   }
-  opts = utils.extend(true, {}, opts);
+  opts = opts || {};
 
   if (typeof fun === 'function') {
     fun = {map : fun};
@@ -874,7 +875,7 @@
   } catch (e) {}
 }
 
-utils.inherits(QueryParseError, Error);
+inherits(QueryParseError, Error);
 
 function NotFoundError(message) {
   this.status = 404;
@@ -886,7 +887,7 @@
   } catch (e) {}
 }
 
-utils.inherits(NotFoundError, Error);
+inherits(NotFoundError, Error);
 
 function BuiltInError(message) {
   this.status = 500;
@@ -898,4 +899,4 @@
   } catch (e) {}
 }
 
-utils.inherits(BuiltInError, Error);
\ No newline at end of file
+inherits(BuiltInError, Error);
\ No newline at end of file
diff --git a/lib/mapreduce/taskqueue.js b/lib/mapreduce/taskqueue.js
index d1eb652..e233c18 100644
--- a/lib/mapreduce/taskqueue.js
+++ b/lib/mapreduce/taskqueue.js
@@ -4,7 +4,7 @@
  * callbacks will eventually fire (once).
  */
 
-var Promise = require('./utils').Promise;
+var Promise = require('../deps/promise');
 
 function TaskQueue() {
   this.promise = new Promise(function (fulfill) {fulfill(); });
diff --git a/lib/mapreduce/utils.js b/lib/mapreduce/utils.js
index fce0f0e..92177a5 100644
--- a/lib/mapreduce/utils.js
+++ b/lib/mapreduce/utils.js
@@ -1,8 +1,5 @@
 'use strict';
 
-exports.Promise = require('../deps/promise');
-exports.inherits = require('inherits');
-exports.extend = require('pouchdb-extend');
 var argsarray = require('argsarray');
 
 exports.promisedCallback = function (promise, callback) {
@@ -78,6 +75,4 @@
     output[i] = keys[i].substring(1);
   }
   return output;
-};
-
-exports.MD5 = require('./md5');
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/lib/setup.js b/lib/setup.js
index e3a2ad9..f7755ed 100644
--- a/lib/setup.js
+++ b/lib/setup.js
@@ -125,7 +125,7 @@
       name = undefined;
     }
 
-    opts = utils.extend(true, {}, defaultOpts, opts);
+    opts = utils.extend({}, defaultOpts, opts);
     PouchDB.call(this, name, opts, callback);
   }
 
@@ -141,7 +141,7 @@
       opts = name;
       name = undefined;
     }
-    opts = utils.extend(true, {}, defaultOpts, opts);
+    opts = utils.extend({}, defaultOpts, opts);
     return PouchDB.destroy(name, opts, callback);
   });
 
diff --git a/lib/utils.js b/lib/utils.js
index ca95e51..89acace 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,6 +1,5 @@
 /*jshint strict: false */
 var merge = require('./merge');
-exports.extend = require('pouchdb-extend');
 exports.ajax = require('./deps/ajax/prequest');
 exports.uuid = require('./deps/uuid');
 exports.getArguments = require('argsarray');
@@ -24,9 +23,8 @@
 // TODO: only used by the integration tests
 exports.binaryStringToBlobOrBuffer = binStringToBlobOrBuffer;
 
-exports.clone = function (obj) {
-  return exports.extend(true, {}, obj);
-};
+exports.clone = require('./deps/clone');
+exports.extend = require('./deps/extend');
 
 exports.pick = require('./deps/pick');
 exports.inherits = require('inherits');
diff --git a/package.json b/package.json
index 9557cee..b79a08a 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
     "miller-rabin": "1.1.1",
     "pouchdb-collate": "^1.2.0",
     "pouchdb-collections": "^1.0.0",
-    "pouchdb-extend": "^0.1.2",
     "pouchdb-upsert": "^1.0.2",
     "request": "~2.28.0",
     "spark-md5": "0.0.5",
diff --git a/tests/integration/test.basics.js b/tests/integration/test.basics.js
index e5786d1..83b98f0 100644
--- a/tests/integration/test.basics.js
+++ b/tests/integration/test.basics.js
@@ -626,6 +626,38 @@
       });
     });
 
+    it('#4126 should not store raw Dates', function () {
+      var date = new Date();
+      var date2 = new Date();
+      var date3 = new Date();
+      var origDocs = [
+        { _id: '1', mydate: date },
+        { _id: '2', array: [date2] },
+        { _id: '3', deep: { deeper: { deeperstill: date3 } }
+        }
+      ];
+      return new PouchDB(dbs.name).then(function (db) {
+        return db.bulkDocs(origDocs).then(function () {
+          return db.allDocs({include_docs: true});
+        }).then(function (res) {
+          var docs = res.rows.map(function (row) {
+            delete row.doc._rev;
+            return row.doc;
+          });
+          docs.should.deep.equal([
+            { _id: '1', mydate: date.toJSON() },
+            { _id: '2', array: [date2.toJSON()] },
+            { _id: '3', deep: { deeper: { deeperstill: date3.toJSON() } }
+            }
+          ]);
+          origDocs[0].mydate.should.be.instanceof(Date, 'date not modified');
+          origDocs[1].array[0].should.be.instanceof(Date, 'date not modified');
+          origDocs[2].deep.deeper.deeperstill.should.be.instanceof(Date,
+            'date not modified');
+        });
+      });
+    });
+
     it('Error when document is not an object', function (done) {
       var db = new PouchDB(dbs.name);
       var doc1 = [{ _id: 'foo' }, { _id: 'bar' }];