diff --git a/makefile b/makefile
index 4b2072a3..654639c6 100644
--- a/makefile
+++ b/makefile
@@ -34,6 +34,7 @@ build:
cp ./src/adapters/indexed-db.js ./lib/lawnchair-adapter-indexed-db-$(VERSION).js
cp ./src/adapters/webkit-sqlite.js ./lib/lawnchair-adapter-webkit-sqlite-$(VERSION).js
cp ./src/adapters/html5-filesystem.js ./lib/lawnchair-adapter-html5-filesystem-$(VERSION).js
+ cp ./src/adapters/touchdb-couchdb.js ./lib/lawnchair-adapter-touchdb-couchdb-$(VERSION).js
min:
java -jar ./util/compiler.jar --js ./lib/lawnchair-$(VERSION).js > ./lib/lawnchair-$(VERSION).min.js
@@ -58,6 +59,7 @@ test:
cp ./src/adapters/indexed-db.js ./test/lib/lawnchair-adapter-indexed-db.js
cp ./src/adapters/webkit-sqlite.js ./test/lib/lawnchair-adapter-webkit-sqlite.js
cp ./src/adapters/html5-filesystem.js ./test/lib/lawnchair-adapter-html5-filesystem.js
+ cp ./src/adapters/touchdb-couchdb.js ./test/lib/lawnchair-adapter-touchdb-couchdb.js
open ./test/index.html
#open ./test/plugin/aggregation.html
diff --git a/src/adapters/touchdb-couchdb.js b/src/adapters/touchdb-couchdb.js
new file mode 100644
index 00000000..b5effe64
--- /dev/null
+++ b/src/adapters/touchdb-couchdb.js
@@ -0,0 +1,251 @@
+// Original author: Chris Anderson jchris@couchbase.com
+// Copyright 2013 Couchbase
+Lawnchair.adapter('touchdb-couchdb', (function(){
+ function makePath (base, path) {
+ var k, q, query = [], first = true, uri;
+ if (path) {
+ if ($.isArray(path)) {
+ if (typeof path[path.length-1] == 'object') {
+ q = path[path.length-1];
+ path = path.slice(0, path.length-1);
+ for (k in q) {
+ if (['startkey', 'endkey', 'start_key', 'end_key', 'key'].indexOf(k) !== -1) {
+ v = JSON.stringify(q[k])
+ } else {
+ v = q[k];
+ }
+ query.push(encodeURIComponent(k)+'='+encodeURIComponent(v.toString()));
+ }
+ query = query.join('&');
+ }
+ uri = (path.map(encodeURIComponent)).join('/');
+ if (query.toString()) {
+ uri = uri + "?" + query.toString();
+ }
+ return base + "/" + uri;
+ } else {
+ return base + "/" + path;
+ }
+ } else {
+ return base;
+ }
+ }
+
+ function makeDb(dbName) {
+ if (location.protocol === 'file:') {
+ console.log("file:// url, so assume TouchDB-iOS is whitelisted at http://localhost.touchdb./")
+ dbName = "http://localhost.touchdb./"+dbName;
+ } else {
+ console.log("assume CouchDB is reachable at "+location.origin);
+ dbName = "/"+dbName;
+ }
+ function dbReq(/* path, json, cb */) {
+ var cb, opts = {
+ contentType : "application/json",
+ type : this
+ };
+ if (typeof arguments[0] == 'function') {
+ // the db path
+ cb = arguments[0];
+ opts.url = makePath(dbName);
+ } else if (typeof arguments[1] == 'function') {
+ // new path
+ cb = arguments[1];
+ opts.url = makePath(dbName, arguments[0]);
+ } else {
+ // we have a body
+ cb = arguments[2];
+ opts.url = makePath(dbName, arguments[0]);
+ opts.data = JSON.stringify(arguments[1]);
+ }
+ opts.complete = function(xhr, code) {
+ var body;
+ try {
+ body = JSON.parse(xhr.responseText);
+ } catch(e) {
+ body = xhr.responseText;
+ }
+ if (xhr.status >= 400) {
+ if (body.error) {
+ cb(body, xhr);
+ } else {
+ cb(xhr.status, body, xhr)
+ }
+ } else {
+ cb(null, body);
+ }
+ };
+ return $.ajax(opts);
+ }
+ function allReq(){
+ return dbReq.apply("GET", arguments);
+ };
+ "get put post head del".split(" ").forEach(function(v){
+ allReq[v] = function() {
+ var type;
+ if (v == "del") {
+ type = "DELETE";
+ } else {
+ type = v.toUpperCase();
+ }
+ return dbReq.apply(type, arguments);
+ }
+ });
+ return allReq;
+ }
+
+ function forceSave(db, objs, cb) {
+ JSON.stringify(objs); // to catch cyclic errors while we can
+ db.post("_all_docs", {keys:objs.map(function(o){return o._id;})},
+ function(err, view) {
+ if (err) return cb(err);
+ for (var i = 0; i < view.rows.length; i++) {
+ if (view.rows[i].value) {
+ objs[i]._rev = view.rows[i].value.rev;
+ }
+ };
+ db.post("_bulk_docs", {docs:objs}, function(err, ok) {
+ if (err) return cb(err);
+ for (var i = 0; i < ok.length; i++) {
+ objs[i]._rev = ok[i].rev;
+ };
+ cb(null, objs);
+ });
+ });
+ };
+
+ return {
+ valid: function() {
+ if ($ && typeof $.ajax == "function") {
+ return true;
+ } else {
+ console.log("touchdb-couchdb Lawnchair adapter requires Zepto or jQuery for $.ajax()");
+ return false;
+ }
+ },
+
+ init: function (options, callback) {
+ var self = this;
+ this.name = options.name;
+ this.db = makeDb(this.name);
+ this.db.put(function(err, ok) {
+ if (err && err.error !== "file_exists") {
+ throw(err);
+ } else {
+ self.fn(self.name, callback).call(self, self);
+ }
+ });
+ return this;
+ },
+
+ keys: function (cb) {
+ var self = this;
+ this.db("_all_docs", function(err, view){
+ var ids = [];
+ if (!err && view.rows) {
+ ids = view.rows.map(function(r){return r.id;});
+ }
+ self.fn('keys', cb).call(this, ids);
+ });
+ return this;
+ },
+ save: function(obj, cb) {
+ obj._id = obj.key = obj.key || this.uuid();
+ var self = this;
+ forceSave(self.db, [obj], function(err){
+ if (err) throw(err);
+ if (cb) {
+ self.lambda(cb).call(self, obj)
+ }
+ });
+ return this;
+ },
+
+ batch: function (objs, cb) {
+ var self = this;
+ objs = objs.map(function(obj) {
+ obj._id = obj.key = obj.key || self.uuid();
+ return obj;
+ });
+ forceSave(self.db, objs, function(err){
+ if (err) throw(err);
+ if (cb) {
+ self.lambda(cb).call(self, objs)
+ }
+ });
+ return this;
+ },
+
+ get: function (keyOrArray, cb) {
+ var self = this;
+ var keys = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray];
+ this.db.post(["_all_docs", {include_docs:true}], {keys:keys}, function(err, view) {
+ if (err) throw (err);
+ var docs = [];
+ view.rows.forEach(function(r){
+ docs.push(r.doc||null);
+ });
+ if (cb) {
+ if (self.isArray(keyOrArray)) {
+ self.lambda(cb).call(self, docs);
+ } else {
+ self.lambda(cb).call(self, docs[0]);
+ }
+ }
+ });
+ return this;
+ },
+
+ exists: function (key, cb) {
+ this.get(key, function(doc){
+ this.lambda(cb).call(this, !!doc);
+ });
+ return this;
+ },
+
+ all: function (cb) {
+ var self = this;
+ this.db(["_all_docs", {include_docs:true}], function(err, view){
+ var docs = [];
+ if (!err && view.rows) {
+ docs = view.rows.map(function(r){return r.doc;});
+ }
+ self.fn(self.name, cb).call(self, docs);
+ });
+ return this;
+ },
+
+ remove: function (keyOrArray, cb) {
+ var dels = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray];
+ var self = this;
+ dels = dels.map(function(d){
+ var id = d.key || d;
+ return {_id : id, _deleted : true};
+ });
+ forceSave(this.db, dels, function(err) {
+ if (err) throw (err);
+ if (cb) self.lambda(cb).call(self);
+ });
+ return this;
+ },
+
+ nuke: function (cb) {
+ // nuke leaves us with an empty database
+ var self = this;
+ this.db.del(function(err, ok) {
+ if (err) {
+ throw(err);
+ } else {
+ self.db.put(function(err, ok) {
+ if (err && !(err.error == "file_exists")) {
+ throw (err);
+ }
+ if (cb) self.lambda(cb).call(self)
+ });
+ }
+ });
+ return this;
+ }
+ }
+/////
+})())
diff --git a/test/index.html b/test/index.html
index 3e906e56..f50a2b56 100755
--- a/test/index.html
+++ b/test/index.html
@@ -15,6 +15,8 @@
+
+