diff --git a/package.json b/package.json index 61f6eb4c..fae7557a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "cookie-parser": "^1.4.3", "corser": "~2.0.0", "couchdb-calculate-session-id": "^1.1.0", + "couchdb-eval": "^1.0.0", "couchdb-harness": "*", "couchdb-log-parse": "^0.0.4", "couchdb-objects": "^1.0.0", @@ -58,6 +59,7 @@ "pouchdb-promise": "^6.1.2", "pouchdb-replication": "^6.1.0", "pouchdb-req-http-query": "^1.0.3", + "couchdb-resp-completer": "^1.0.0", "pouchdb-rewrite": "^1.0.7", "pouchdb-show": "^1.0.8", "pouchdb-size": "^1.2.2", diff --git a/packages/node_modules/pouchdb-update/README.md b/packages/node_modules/pouchdb-update/README.md new file mode 100644 index 00000000..9898a44c --- /dev/null +++ b/packages/node_modules/pouchdb-update/README.md @@ -0,0 +1,9 @@ +pouchdb-update +============== + +A PouchDB plug-in that allows you to re-use your CouchDB update +functions on the client side. A browser version is available. + +See also [pouchdb-update's documentation](http://pythonhosted.org/Python-PouchDB/js-plugins.html#pouchdb-update-plug-in) + +[Website of this plug-in and a few others](http://python-pouchdb.marten-de-vries.nl/plugins.html) diff --git a/packages/node_modules/pouchdb-update/index.js b/packages/node_modules/pouchdb-update/index.js new file mode 100644 index 00000000..26ad5bd8 --- /dev/null +++ b/packages/node_modules/pouchdb-update/index.js @@ -0,0 +1,124 @@ +/* + Copyright 2013-2014, Marten de Vries + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +"use strict"; + +var Promise = require("pouchdb-promise"); + +var couchdb_objects = require("couchdb-objects"); +var nodify = require("promise-nodify"); +var coucheval = require("couchdb-eval"); +var httpQuery = require("pouchdb-req-http-query"); +var completeRespObj = require("couchdb-resp-completer"); +var PouchPluginError = require('pouchdb-plugin-error'); + +exports.update = function (updatePath, options, callback) { + if (["function", "undefined"].indexOf(typeof options) !== -1) { + callback = options; + options = {}; + } + var db = this; + + //better default than GET. + options.method = options.method || "POST"; + + var designDocName = updatePath.split("/")[0]; + var updateName = updatePath.split("/")[1]; + var docId = updatePath.split("/").slice(2).join("/"); + + //build request object + var pathEnd = ["_design", designDocName, "_update", updateName]; + if (docId) { + pathEnd.push.apply(pathEnd, docId.split("/")); + } + var reqPromise = couchdb_objects.buildRequestObject(db, pathEnd, options); + var promise = reqPromise.then(function (req) { + //the only option that isn't related to the request object. + delete req.withValidation; + + //because we might have set method -> POST, also set a Content-Type + //to prevent a Qt warning in case there isn't one. + var h = req.headers; + h["Content-Type"] = h["Content-Type"] || "application/x-www-form-urlencoded"; + + if (["http", "https"].indexOf(db.type()) === -1) { + return offlineQuery(db, designDocName, updateName, docId, req, options); + } else { + return httpQuery(db, req); + } + }); + nodify(promise, callback); + return promise; +}; + +function offlineQuery(db, designDocName, updateName, docId, req, options) { + if (req.method === "GET") { + return Promise.reject(new PouchPluginError({ + status: 500, // should be 405, but for CouchDB compatibility... + name: "method_not_allowed", + message: "Update functions do not allow GET" + })); + } + + //get the documents involved + var ddocPromise = db.get("_design/" + designDocName).then(function (designDoc) { + if (!(designDoc.updates || {}).hasOwnProperty(updateName)) { + throw new PouchPluginError({ + status: 404, + name: "not_found", + message: "missing update function " + updateName + " on design doc _design/" + designDocName + }); + } + return designDoc; + }); + var docPromise = db.get(docId).catch(function () { + //doc might not exist - that's ok and expected. + return null; + }); + + return Promise.all([ddocPromise, docPromise]) + .then(Function.prototype.apply.bind(function (designDoc, doc) { + //run update function + var func = coucheval.evaluate(designDoc, {}, designDoc.updates[updateName]); + var result; + try { + result = func.call(designDoc, doc, req); + } catch (e) { + throw coucheval.wrapExecutionError(e); + } + var code = (result[1] || {}).code; + var couchResp = completeRespObj(result[1]); + function setCode(proposedCode) { + couchResp.code = code || proposedCode; + } + //save result[0] if necessary + var savePromise = Promise.resolve(); + if (result[0] === null) { + setCode(200); + } else { + var methodName = options.withValidation ? "validatingPut" : "put"; + savePromise = db[methodName](result[0]).then(function (resp) { + couchResp.headers['X-Couch-Id'] = resp.id; + couchResp.headers['X-Couch-Update-NewRev'] = resp.rev; + setCode(201); + }); + } + //then return the result + return savePromise.then(function () { + return couchResp; + }); + }, null)); +} diff --git a/packages/node_modules/pouchdb-update/package.json b/packages/node_modules/pouchdb-update/package.json new file mode 100644 index 00000000..4a2153fc --- /dev/null +++ b/packages/node_modules/pouchdb-update/package.json @@ -0,0 +1,20 @@ +{ + "name": "pouchdb-update", + "main": "index.js", + "description": "A PouchDB plug-in that allows you to re-use your CouchDB update functions on the client side.", + "repository": { + "type": "git", + "url": "https://github.com/pouchdb/pouchdb-server.git" + }, + "keywords": [ + "pouch", + "pouchdb", + "couch", + "couchdb", + "update", + "design", + "handler" + ], + "license": "Apache-2.0", + "author": "Marten de Vries" +} diff --git a/tests/pouchdb-update/features.js b/tests/pouchdb-update/features.js new file mode 100644 index 00000000..69de6b21 --- /dev/null +++ b/tests/pouchdb-update/features.js @@ -0,0 +1,114 @@ +const {setup, setupWithDoc, teardown, updateDocument, should, shouldThrowError} = require('./utils'); + +describe('Sync update tests', () => { + let db; + + beforeEach(() => { + return setupWithDoc() + + .then((result) => { + db = result.db; + return db.put(updateDocument); + }); + }); + afterEach(teardown); + + it('args', () => { + return db.update('test/args/mytest', {query: {'a': 3}}) + + .then((response) => { + const [doc, req] = JSON.parse(response.body); + doc.test.should.be.ok; + req.id.should.equal('mytest'); + req.raw_path.should.equal('/test/_design/test/_update/args/mytest?a=3'); + }); + }); + + it('args without doc', () => { + return db.update('test/args', {withValidation: true}) + + .then((response) => { + const [doc, req] = JSON.parse(response.body); + should.equal(doc, null); + req.should.not.have.property('withValidation'); + }); + }); + + it('missing function', () => { + shouldThrowError(() => { + return db.update('test/missing/mytest'); + }) + + .then((error) => { + error.toString().should.be.ok; + error.name.should.equal('not_found'); + error.message.should.equal('missing update function missing on design doc _design/test'); + }); + }); + + it('saving', () => { + db.update('test/save-adding-date', {body: JSON.stringify({ + _id: 'test', + name: 'Today' + })}) + + .then((response) => { + response.body.should.equal('Hello World!'); + + return db.get('test'); + }) + + .then((doc) => { + doc.updated.should.be.ok; + doc.name.should.equal('Today'); + }); + }); +}); + +describe('Async update tests', () => { + let db; + + beforeEach(() => { + db = setup(); + return db.put(updateDocument); + }); + afterEach(teardown); + + it('exception', () => { + return db.update('test/exception') + + .then(() => { + 'db.update("test/exception") should not resolve'.should.equal(''); + }) + + .catch((error) => { + error.status.should.equal(500); + error.name.should.equal('ReferenceError'); + error.message.should.contain('abc'); + }); + }); +}); + +describe('Async update with empty design doc', () => { + let db; + + beforeEach(() => { + db = setup(); + return db.put({_id: '_design/test'}); + }); + afterEach(teardown); + + it('basic', () => { + return db.update('test/missing') + + .then(() => { + 'db.update("test/missing") should not resolve'.should.equal(''); + }) + + .catch((error) => { + error.status.should.equal(404); + error.name.should.equal('not_found'); + error.message.should.equal('missing update function missing on design doc _design/test'); + }); + }); +}); diff --git a/tests/pouchdb-update/http.js b/tests/pouchdb-update/http.js new file mode 100644 index 00000000..b411c57a --- /dev/null +++ b/tests/pouchdb-update/http.js @@ -0,0 +1,21 @@ +const {setupHTTP, teardown, updateDocument, should} = require('./utils'); + +let db; + +describe('http tests', () => { + beforeEach(() => { + db = setupHTTP(); + return db.put(updateDocument); + }); + afterEach(teardown); + + it('update', () => { + return db.update('test/args/my-id') + + .then((result) => { + const [doc, req] = JSON.parse(result.body); + should.not.exist(doc); + req.id.should.equal('my-id'); + }); + }); +}); diff --git a/tests/pouchdb-update/signatures.js b/tests/pouchdb-update/signatures.js new file mode 100644 index 00000000..f1cf25f9 --- /dev/null +++ b/tests/pouchdb-update/signatures.js @@ -0,0 +1,15 @@ +const {setup, teardown} = require('./utils'); + +describe('signature tests', () => { + let db; + beforeEach(() => { + db = setup(); + }); + afterEach(teardown); + + it('update', () => { + const promise = db.update('test/test/test', () => {}); + promise.then.should.be.ok; + promise.catch.should.be.ok; + }); +}); diff --git a/tests/pouchdb-update/utils.js b/tests/pouchdb-update/utils.js new file mode 100644 index 00000000..446607e4 --- /dev/null +++ b/tests/pouchdb-update/utils.js @@ -0,0 +1,23 @@ +const stuff = require('pouchdb-plugin-helper/testutils'); +const Update = require('../../packages/node_modules/pouchdb-update'); + +stuff.PouchDB.plugin(Update); + +stuff.updateDocument = { + _id: "_design/test", + updates: { + args: `function (doc, req) { + return [null, toJSON([doc, req])]; + }`, + exception: `function (doc, req) { + return abc; + }`, + 'save-adding-date': `function (oldDoc, req) { + var doc = JSON.parse(req.body); + doc.updated = new Date(); + return [doc, "Hello World!"]; + }` + } +}; + +module.exports = stuff;