adding actions
diff --git a/actions/account-actions/create-database.js b/actions/account-actions/create-database.js
new file mode 100755
index 0000000..eeda689
--- /dev/null
+++ b/actions/account-actions/create-database.js
@@ -0,0 +1,65 @@
+ /**
+ * Create database in Cloudant account:
+ * https://docs.cloudant.com/database.html#get-databases
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  if (!dbName) {
+    return whisk.error('dbname is required.');
+  }
+
+  var promise = createDatabase(cloudant, dbName);
+  return promise;
+
+}
+
+function createDatabase(cloudant, dbName) {
+  return new Promise(function(resolve, reject) {
+    cloudant.db.create(dbName, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/account-actions/delete-database.js b/actions/account-actions/delete-database.js
new file mode 100755
index 0000000..f4e414f
--- /dev/null
+++ b/actions/account-actions/delete-database.js
@@ -0,0 +1,63 @@
+/**
+ * Delete database from Cloudant account:
+ * https://docs.cloudant.com/database.html#deleting-a-database
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  if (!dbName) {
+    return whisk.error('dbname is required.');
+  }
+
+  return destroyDatabase(cloudant, dbName);
+}
+
+function destroyDatabase(cloudant, dbName) {
+  return new Promise(function(resolve, reject) {
+    cloudant.db.destroy(dbName, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/account-actions/list-all-databases.js b/actions/account-actions/list-all-databases.js
new file mode 100755
index 0000000..da44b1d
--- /dev/null
+++ b/actions/account-actions/list-all-databases.js
@@ -0,0 +1,61 @@
+/**
+ * Get all databases in Cloudant account:
+ * https://docs.cloudant.com/database.html#get-databases
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+
+  return listAllDatabases(cloudant);
+}
+
+function listAllDatabases(cloudant) {
+  return new Promise(function(resolve, reject) {
+    cloudant.db.list(function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        //Response is an array and only JSON objects can be passed to whisk.done
+        var responseObj = {};
+        responseObj.all_databases = response;
+        resolve(responseObj);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/account-actions/read-database.js b/actions/account-actions/read-database.js
new file mode 100755
index 0000000..6744c97
--- /dev/null
+++ b/actions/account-actions/read-database.js
@@ -0,0 +1,62 @@
+/**
+ * Read database in Cloudant account:
+ * https://docs.cloudant.com/database.html#read
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  if (!dbName) {
+    return whisk.error('dbname is required.');
+  }
+
+  return readDatabase(cloudant, dbName);
+}
+
+function readDatabase(cloudant, dbName) {
+  return new Promise(function(resolve, reject) {
+    cloudant.db.get(dbName, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/account-actions/read-updates-feed.js b/actions/account-actions/read-updates-feed.js
new file mode 100755
index 0000000..24ad092
--- /dev/null
+++ b/actions/account-actions/read-updates-feed.js
@@ -0,0 +1,69 @@
+/**
+ * Read updates feed from Cloudant database:
+ * https://docs.cloudant.com/advanced.html#get-/_db_updates
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var params = {};
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return readUpdatesFeed(cloudant, params);
+}
+
+function readUpdatesFeed(cloudant, params) {
+  return new Promise(function(resolve, reject) {
+    cloudant.updates(params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/changes.js b/actions/changes.js
new file mode 100644
index 0000000..9b0ef08
--- /dev/null
+++ b/actions/changes.js
@@ -0,0 +1,82 @@
+var request = require('request');
+
+function main(msg) {
+    console.log("cloudant trigger feed: ", msg);
+
+    // lifecycleEvent is one of [ create, delete ]
+    var lifecycleEvent = (msg.lifecycleEvent || 'CREATE').trim().toUpperCase();
+
+    // whisk trigger to fire
+    var trigger = msg.triggerName;
+    var replaceNameTrigger = trigger.replace(/\//g, ":");
+
+    // configuration parameters
+    var provider_endpoint = msg.package_endpoint;
+    var dbname = msg.dbname;
+    var user = msg.username;
+    var pass = msg.password;
+    var includeDoc = msg.includeDoc || false;
+    var host = msg.host;
+    var maxTriggers = msg.maxTriggers || 1000;
+
+    if (lifecycleEvent == 'CREATE') {
+        // auth key for trigger
+        var apiKey = msg.authKey;
+        var auth = apiKey.split(':');
+        var input = {};
+        input["accounturl"] = "https://" + host;
+        input["dbname"] = dbname;
+        input["user"] = user;
+        input["pass"] = pass;
+        input["includeDoc"] = includeDoc;
+        input["apikey"] = apiKey;
+        input["maxTriggers"] = maxTriggers;
+        input["callback"] = {};
+        input["callback"]["action"] = {};
+        input["callback"]["action"]["name"] = trigger;
+
+        return cloudantHelper(provider_endpoint, 'put', replaceNameTrigger, auth, input);
+    } else if (lifecycleEvent == 'DELETE') {
+        return cloudantHelper(provider_endpoint, 'delete', replaceNameTrigger);
+    } else {
+        return whisk.error('operation is neither CREATE or DELETE');
+    }
+}
+
+function cloudantHelper(endpoint, verb, name, auth, input) {
+    var uri = 'http://' + endpoint + '/cloudanttriggers/' + name;
+    var options = {
+        method : verb,
+        uri : uri
+    };
+
+    if(auth){
+        options.auth = {
+            user : auth[0],
+            pass : auth[1]
+        }
+    }
+
+    if (verb === 'put') {
+        options.json = input;
+    }
+
+    var promise = new Promise(function(resolve, reject) {
+      request(options, function(error, response, body) {
+          console.log('cloudant trigger feed: done http request', '[error:]', error);
+          if (!error && response.statusCode == 200) {
+              console.log(body);
+              resolve();
+          } else {
+              if (response) {
+                  console.log('response code:', response.statusCode);
+              } else {
+                  console.log('no response');
+              }
+              reject(error);
+          }
+      });
+    });
+
+    return promise;
+}
diff --git a/actions/database-actions/create-document.js b/actions/database-actions/create-document.js
new file mode 100755
index 0000000..9de6c85
--- /dev/null
+++ b/actions/database-actions/create-document.js
@@ -0,0 +1,94 @@
+/**
+ * Create a document in Cloudant database:
+ * https://docs.cloudant.com/document.html#documentCreate
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var doc = message.doc;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!doc) {
+    return whisk.error('doc is required.');
+  }
+
+  if (typeof message.doc === 'object') {
+    doc = message.doc;
+  } else if (typeof message.doc === 'string') {
+    try {
+      doc = JSON.parse(message.doc);
+    } catch (e) {
+      return whisk.error('doc field cannot be parsed. Ensure it is valid JSON.');
+    }
+  } else {
+    return whisk.error('doc field is ' + (typeof doc) + ' and should be an object or a JSON string.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return insert(cloudantDb, doc, params);
+}
+
+/**
+ * Create document in database.
+ */
+function insert(cloudantDb, doc, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.insert(doc, params, function(error, response) {
+      if (!error) {
+        console.log("success", response);
+        resolve(response);
+      } else {
+        console.log("error", error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/create-query-index.js b/actions/database-actions/create-query-index.js
new file mode 100755
index 0000000..2507e6b
--- /dev/null
+++ b/actions/database-actions/create-query-index.js
@@ -0,0 +1,78 @@
+/**
+ * Create a Cloudant index:
+ * https://docs.cloudant.com/cloudant_query.html#creating-an-index
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var index = message.index;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!index) {
+    return whisk.error('index is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return createIndex(cloudantDb, index);
+}
+
+function createIndex(cloudantDb, index) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.index(index, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(response);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/create-update-attachment.js b/actions/database-actions/create-update-attachment.js
new file mode 100755
index 0000000..de9e110
--- /dev/null
+++ b/actions/database-actions/create-update-attachment.js
@@ -0,0 +1,98 @@
+/**
+ * Create and update attachment for document in Cloudant database:
+ * https://docs.cloudant.com/attachments.html#create-/-update
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var attName = message.attachmentname;
+  var att = message.attachment;
+  var contentType = message.contenttype;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!attName) {
+    return whisk.error('attachmentname is required.');
+  }
+  if(!att) {
+    return whisk.error('attachment is required.');
+  }
+  if(!contentType) {
+    return whisk.error('contenttype is required.');
+  }
+  //Add document revision to query if it exists
+  if(typeof message.docrev !== 'undefined') {
+    params.rev = message.docrev;
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return insert(cloudantDb, docId, attName, att, contentType, params);
+}
+
+/**
+ * Insert attachment for document in database.
+ */
+function insert(cloudantDb, docId, attName, att, contentType, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.attachment.insert(docId, attName, att, contentType, params, function(error, response) {
+      if (!error) {
+        console.log("success", response);
+        resolve(response);
+      } else {
+        console.log("error", error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/delete-attachment.js b/actions/database-actions/delete-attachment.js
new file mode 100755
index 0000000..dacb951
--- /dev/null
+++ b/actions/database-actions/delete-attachment.js
@@ -0,0 +1,93 @@
+/**
+ * Delete attachment for document in Cloudant database:
+ * https://docs.cloudant.com/attachments.html#delete
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var attName = message.attachmentname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!attName) {
+    return whisk.error('attachmentname is required.');
+  }
+  //Add document revision to query if it exists
+  if(typeof message.docrev !== 'undefined') {
+    params.rev = message.docrev;
+  } else {
+    return whisk.error('docrev is required.');
+  }
+
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return deleteAttachment(cloudantDb, docId, attName, params);
+}
+
+/**
+ * Delete attachment for document in database.
+ */
+function deleteAttachment(cloudantDb, docId, attName, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.attachment.destroy(docId, attName, params, function(error, response) {
+      if (!error) {
+        console.log("success", response);
+        resolve(response);
+      } else {
+        console.log("error", error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/delete-document.js b/actions/database-actions/delete-document.js
new file mode 100755
index 0000000..7448e65
--- /dev/null
+++ b/actions/database-actions/delete-document.js
@@ -0,0 +1,76 @@
+/**
+ * Delete a document from Cloudant database:
+ * https://docs.cloudant.com/document.html#delete
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var docRev = message.docrev;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!docRev) {
+    return whisk.error('docrev is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  return destroy(cloudantDb, docId, docRev);
+
+}
+
+/**
+ * Delete document by id and rev.
+ */
+function destroy(cloudantDb, docId, docRev) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.destroy(docId, docRev, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/delete-query-index.js b/actions/database-actions/delete-query-index.js
new file mode 100755
index 0000000..522be2e
--- /dev/null
+++ b/actions/database-actions/delete-query-index.js
@@ -0,0 +1,86 @@
+/**
+ * Delete a Cloudant index:
+ * https://docs.cloudant.com/cloudant_query.html#deleting-an-index
+ **/
+
+var DESIGN_PREFIX = '_design/';
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var indexName = message.indexname;
+  var indexType = message.indextype;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!indexName) {
+    return whisk.error('indexname is required.');
+  }
+  if(!indexType) {
+    return whisk.error('indextype is required.');
+  }
+
+  return deleteIndexFromDesignDoc(cloudant, docId, indexName, indexType, dbName);
+}
+
+function deleteIndexFromDesignDoc(cloudant, docId, indexName, indexType, dbName) {
+
+  return new Promise(function(resolve, reject) {
+    var path = "_index/" + encodeURIComponent(docId)
+      + '/' + encodeURIComponent(indexType)
+      + '/' + encodeURIComponent(indexName);
+
+    cloudant.request({ db: encodeURIComponent(dbName),
+        method : 'delete',
+        path : path
+      }, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/delete-view.js b/actions/database-actions/delete-view.js
new file mode 100755
index 0000000..ae018be
--- /dev/null
+++ b/actions/database-actions/delete-view.js
@@ -0,0 +1,115 @@
+/**
+ * Delete a view from design document in Cloudant database:
+ * https://docs.cloudant.com/design_documents.html
+ **/
+
+var DESIGN_PREFIX = '_design/';
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var viewName = message.viewname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if(!viewName) {
+    return whisk.error('viewname is required.');
+  }
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return deleteViewFromDesignDoc(cloudantDb, docId, viewName, params);
+}
+
+function deleteViewFromDesignDoc(cloudantDb, docId, viewName, params) {
+  //Check that doc id contains _design prefix
+  if (docId.indexOf(DESIGN_PREFIX) !== 0) {
+    docId = DESIGN_PREFIX + docId;
+  }
+
+  return getDocument(cloudantDb, docId)
+    .then(function(document) {
+        console.log("Got document: " + document);
+        delete document.views[viewName];
+
+        //Update the design document after removing the view
+        return insert(cloudantDb, document, params);
+    });
+}
+
+function getDocument(cloudantDb, docId) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.get(docId, function(error, response) {
+      if (!error) {
+        console.log("Got response: " + response);
+        resolve(response);
+      } else {
+        console.log("Got error: " + error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function insert(cloudantDb, doc, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.insert(doc, params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/exec-query-find.js b/actions/database-actions/exec-query-find.js
new file mode 100755
index 0000000..2c85b1e
--- /dev/null
+++ b/actions/database-actions/exec-query-find.js
@@ -0,0 +1,81 @@
+/**
+ * Query using a Cloudant Query index:
+ * https://docs.cloudant.com/cloudant_query.html#finding-documents-using-an-index
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var query = message.query;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!query) {
+    return whisk.error('query field is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.query === 'object') {
+    query = message.query;
+  } else if (typeof message.query === 'string') {
+    try {
+      query = JSON.parse(message.query);
+    } catch (e) {
+      return whisk.error('query field cannot be parsed. Ensure it is valid JSON.');
+    }
+  } else {
+    return whisk.error('query field is ' + (typeof query) + ' and should be an object or a JSON string.');
+  }
+
+  return queryIndex(cloudantDb, query);
+
+}
+
+function queryIndex(cloudantDb, query) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.find(query, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/exec-query-search.js b/actions/database-actions/exec-query-search.js
new file mode 100755
index 0000000..75d4f92
--- /dev/null
+++ b/actions/database-actions/exec-query-search.js
@@ -0,0 +1,89 @@
+/**
+ * Query with Cloudant search:
+ * https://docs.cloudant.com/search.html#queries
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var indexName = message.indexname;
+  var search = message.search;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!indexName) {
+    return whisk.error('indexname is required.');
+  }
+  if(!search) {
+    return whisk.error('search query is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  //Search should be in the form of {"q":"index:my query"}
+  if (typeof message.search === 'object') {
+    search = message.search;
+  } else if (typeof message.search === 'string') {
+    try {
+      search = JSON.parse(message.search);
+    } catch (e) {
+      return whisk.error('search field cannot be parsed. Ensure it is valid JSON.');
+    }
+  } else {
+    return whisk.error('search field is ' + (typeof search) + ' and should be an object or a JSON string.');
+  }
+
+  return querySearch(cloudantDb, docId, indexName, search);
+}
+
+function querySearch(cloudantDb, designDocId, designViewName, search) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.search(designDocId, designViewName, search, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/exec-query-view.js b/actions/database-actions/exec-query-view.js
new file mode 100755
index 0000000..dc1a611
--- /dev/null
+++ b/actions/database-actions/exec-query-view.js
@@ -0,0 +1,86 @@
+/**
+ * Execute view against Cloudant database:
+ * https://docs.cloudant.com/creating_views.html#using-views
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var viewName = message.viewname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!viewName) {
+    return whisk.error('viewname is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return queryView(cloudantDb, docId, viewName, params);
+}
+
+/**
+ * Get view by design doc id and view name.
+ */
+function queryView(cloudantDb, designDocId, designDocViewName, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.view(designDocId, designDocViewName, params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/list-design-documents.js b/actions/database-actions/list-design-documents.js
new file mode 100755
index 0000000..8cd60eb
--- /dev/null
+++ b/actions/database-actions/list-design-documents.js
@@ -0,0 +1,79 @@
+/**
+ * List design documents in Cloudant database:
+ * https://docs.cloudant.com/design_documents.html
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var includeDocs = message.includedocs;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+  //Add start and end key to get _design docs
+  params.startkey = '_design'.toString();
+  params.endkey = '_design0'.toString();
+
+  //If includeDoc exists and is true, add field to additional params object
+  includeDocs = includeDocs.toString().trim().toLowerCase();
+  console.log('includeDocs: ' + includeDocs);
+  if(includeDocs === 'true') {
+    params.include_docs = 'true';
+  }
+
+  return listDesignDocuments(cloudantDb, params);
+}
+
+/**
+ * List design documents.
+ **/
+function listDesignDocuments(cloudantDb, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.list(params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error("error", error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/list-documents.js b/actions/database-actions/list-documents.js
new file mode 100755
index 0000000..1a576de
--- /dev/null
+++ b/actions/database-actions/list-documents.js
@@ -0,0 +1,78 @@
+/**
+ * Get all documents in Cloudant database:
+ * https://docs.cloudant.com/database.html#get-documents
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return listAllDocuments(cloudantDb, params);
+}
+
+/**
+ * List all documents.
+ */
+function listAllDocuments(cloudantDb, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.list(params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error("error", error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/list-query-indexes.js b/actions/database-actions/list-query-indexes.js
new file mode 100755
index 0000000..b91fc98
--- /dev/null
+++ b/actions/database-actions/list-query-indexes.js
@@ -0,0 +1,64 @@
+/**
+ * List Cloudant Query indexes in Cloudant database:
+ * https://docs.cloudant.com/cloudant_query.html#list-all-cloudant-query-indexes
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  return index(cloudantDb);
+}
+
+function index(cloudantDb) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.index(function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/manage-bulk-documents.js b/actions/database-actions/manage-bulk-documents.js
new file mode 100755
index 0000000..e99851c
--- /dev/null
+++ b/actions/database-actions/manage-bulk-documents.js
@@ -0,0 +1,93 @@
+/**
+ * Create, Update, and Delete documents in bulk:
+ * https://docs.cloudant.com/document.html#bulk-operations
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docs = message.docs;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docs) {
+    return whisk.error('docs is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  if (typeof message.docs === 'object') {
+    docs = message.docs;
+  } else if (typeof message.docs === 'string') {
+    try {
+      docs = JSON.parse(message.docs);
+    } catch (e) {
+      return whisk.error('docs field cannot be parsed. Ensure it is valid JSON.');
+    }
+  } else {
+    return whisk.error('docs field is ' + (typeof docs) + ' and should be an object or a JSON string.');
+  }
+
+  return bulk(cloudantDb, docs, params);
+}
+
+function bulk(cloudantDb, docs, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.bulk(docs, params, function(error, response) {
+      if (!error) {
+        var responseDocs = {};
+        responseDocs.docs = response;
+        console.log('success', response);
+        resolve(responseDocs);
+      } else {
+        console.log('Error: ', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/read-attachment.js b/actions/database-actions/read-attachment.js
new file mode 100755
index 0000000..a4827af
--- /dev/null
+++ b/actions/database-actions/read-attachment.js
@@ -0,0 +1,91 @@
+/**
+ * Read attachment for document in Cloudant database:
+ * https://docs.cloudant.com/attachments.html#read
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var attName = message.attachmentname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  if(!attName) {
+    return whisk.error('attachmentname is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  //Add document revision to query if it exists
+  if(typeof message.docrev != 'undefined') {
+    params.rev = message.docrev;
+  }
+
+  return read(cloudantDb, docId, attName, params);
+}
+
+/**
+ * Read attachment for document in database.
+ */
+function read(cloudantDb, docId, attName, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.attachment.get(docId, attName, params, function(error, response) {
+      if (!error) {
+        console.log("success", response);
+        resolve(response);
+      } else {
+        console.log("error", error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/read-changes-feed.js b/actions/database-actions/read-changes-feed.js
new file mode 100755
index 0000000..256ff04
--- /dev/null
+++ b/actions/database-actions/read-changes-feed.js
@@ -0,0 +1,74 @@
+/**
+ * Read changes feed from Cloudant database:
+ * https://docs.cloudant.com/database.html#get-changes
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return changes(cloudant, dbName, params);
+}
+
+function changes(cloudant, dbName, params) {
+  return new Promise(function(resolve, reject) {
+    cloudant.db.changes(dbName, params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/read-document.js b/actions/database-actions/read-document.js
new file mode 100755
index 0000000..bd1631f
--- /dev/null
+++ b/actions/database-actions/read-document.js
@@ -0,0 +1,79 @@
+/**
+ * Read a document in Cloudant database:
+ * https://docs.cloudant.com/document.html#read
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var docId = message.docid;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+  if(!docId) {
+    return whisk.error('docid is required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return readDocument(cloudantDb, docId, params);
+}
+
+function readDocument(cloudantDb, docId, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.get(docId, params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.error('error', error);
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}
diff --git a/actions/database-actions/update-document.js b/actions/database-actions/update-document.js
new file mode 100755
index 0000000..6486502
--- /dev/null
+++ b/actions/database-actions/update-document.js
@@ -0,0 +1,94 @@
+/**
+ * Update a document in Cloudant database:
+ * https://docs.cloudant.com/document.html#update
+ **/
+
+function main(message) {
+  var cloudantOrError = getCloudantAccount(message);
+  if (typeof cloudantOrError !== 'object') {
+    return whisk.error('getCloudantAccount returned an unexpected object type.');
+  }
+  var cloudant = cloudantOrError;
+  var dbName = message.dbname;
+  var doc = message.doc;
+  var params = {};
+
+  if(!dbName) {
+    return whisk.error('dbname is required.');
+  }
+
+  if (typeof message.doc === 'object') {
+    doc = message.doc;
+  } else if (typeof message.doc === 'string') {
+    try {
+      doc = JSON.parse(message.doc);
+    } catch (e) {
+      return whisk.error('doc field cannot be parsed. Ensure it is valid JSON.');
+    }
+  } else {
+    return whisk.error('doc field is ' + (typeof doc) + ' and should be an object or a JSON string.');
+  }
+  if(!doc || !doc.hasOwnProperty("_rev")) {
+    return whisk.error('doc and doc._rev are required.');
+  }
+  var cloudantDb = cloudant.use(dbName);
+
+  if (typeof message.params === 'object') {
+    params = message.params;
+  } else if (typeof message.params === 'string') {
+    try {
+      params = JSON.parse(message.params);
+    } catch (e) {
+      return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');
+    }
+  }
+
+  return insert(cloudantDb, doc, params);
+}
+
+/**
+ * Inserts updated document into database.
+ */
+function insert(cloudantDb, doc, params) {
+  return new Promise(function(resolve, reject) {
+    cloudantDb.insert(doc, params, function(error, response) {
+      if (!error) {
+        console.log('success', response);
+        resolve(response);
+      } else {
+        console.log('error', error)
+        reject(error);
+      }
+    });
+  });
+}
+
+function getCloudantAccount(message) {
+  // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+  // when the username contains dashes (common in Bluemix scenarios)
+  var cloudantUrl;
+
+  if (message.url) {
+    // use bluemix binding
+    cloudantUrl = message.url;
+  } else {
+    if (!message.host) {
+      whisk.error('cloudant account host is required.');
+      return;
+    }
+    if (!message.username) {
+      whisk.error('cloudant account username is required.');
+      return;
+    }
+    if (!message.password) {
+      whisk.error('cloudant account password is required.');
+      return;
+    }
+
+    cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+  }
+
+  return require('cloudant')({
+    url: cloudantUrl
+  });
+}