Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

couchdb api now dynamically generated.

  • Loading branch information...
commit 110f842b31f633b8c3c5a904860d37a8aae687f6 1 parent 47d505f
@dgreisen authored
View
118 README.md
@@ -1,18 +1,19 @@
# YACA #
-## Custom-generated CouchDB api through introspection ##
+## Custom-generated CouchDB api through database introspection ##
-You do not use YACA directly. Instead, you run the script `generate_couch_api` which introspects your CouchDB instance and generates a custom api. You then import that custom API into your node project and make api calls on it. This allows you to make intuitive and easy calls to your DB. for example, to request all documents from the 'master' view of the '_design/app' design document of your 'primary' database you simply make the following call:
+Simply import YACA, and call the factory function. The factory function introspects your CouchDB instance and returns a custom api. This allows you to make intuitive and easy calls to your database, and inspect your database from the commandline. For example, to request all documents from the 'master' view of the '_design/app' design document of your 'primary_db' database you simply make the following call:
- couchdb = require('couchdb') // import our generated api
- couchdb.primary.app.views.master(callback)
- callback = function(error, response, body) {
- console.log(body)
- }
+ api_factory = require('YACA');
+ api_factory(function(error, couchdb_api) {
+ couchdb_api.primary_db.app.views.master(function(error, response, body) {
+ console.log(body)
+ })
+ })
It is that simple.
## General Design ##
-YACA was designed for CouchDB databases where the number of databases and the design documents remain relatively stable. In the future, YACA will be able to introspect DB changes at runtime; however, currently, you must run YACA every time a DB is added/removed, or a design document is modified.
+YACA was designed for CouchDB databases where the number of databases and the design documents remain relatively stable. As such, the structure of the database is cached, and the api is generated from this cached structure unless `update: true` is explicitly passed to the factory. In a future release, YACA will be able to introspect DB changes at will; however, currently, you must create a new couchdb_api instance with the api_factory every time the database is added/removed, or a design document is modified.
YACA is based on Mikeal's [request](https://github.com/mikeal/request/) module. Therefore, with a few exceptions listed below, api commands accept an options
argument and a callback. They return an error, the original response, and the parsed JSON body. Because it is based on request, you can do fancy things like piping, etc. made possible by Mikeal's module. See <https://github.com/mikeal/request> for more info.
@@ -26,22 +27,13 @@ argument and a callback. They return an error, the original response, and the pa
### 1. Generate API ###
First, generate the API by calling:
- generate_couch_api [-f PATH] [-d DB_URL] [-a USERNAME:PASSWORD]
-
- * `PATH` is the location of the file into which to write the couchdb api. If not specified, it will be placed in path/to/YACA/lib/couchdb.js, and will be imported when you `require('YACA')`.
- * `DB_URL` is the url to your CouchDB instance. It defaults to `http://127.0.0.1:5984`. If privileges are needed to view design documents or the root instance, provide username and password as: `http://username:password@host_name`
- * provide `USERNAME:PASSWORD` where `USERNAME` is an admin username and `PASSWORD` is the admin's password, if you wish to be able to access admin-restricted content through the api. *The username and password will be stored in the generated API code.* See [options reference](#options) for more.
-
-You must regenerate the API whenever you add or remove a database, or you modify a design document.
+ api_factory = require('YACA');
+ api_factory(factory_options, callback)
-### 2. Import the generated API ###
-If you used the defaults to generate your custom API, then you can import that api by:
+ * `factory_options` - an optional hash. See [factory options](#factory_options) reference for more.
+ * `callback` - a callback function of the form `callback(error, couchdb_api)`.
- couchdb = require("YACA");
-
-If you specified a different `PATH`, simply import from that file. The rest of this document assumes you have imported the generated api into a variable called`couchdb`.
-
-### 3. Use the API ###
+### 2. Use the API ###
There are four primary API methods, corresponding to the http methods:
* `get`
* `put`
@@ -52,17 +44,17 @@ To make a call against a database:
couchdb.{{database_name}}.{{http_method}}(options, callback)
-or against a particular
+or against a particular handler:
couchdb.{{database_name}}
.{{design_doc_name}}
- .{{handler_name>
+ .{{handler_name}}
.{{method}}
.{{http_method}}(options, callback)
See [options](#options) for what is allowed in options. callback is of the form: `callback(error, response, body)` where `response` is the unadulterated response from the CouchDB server and `body` is a javascript object parsed from the JSON response body.
-There are some [helper functions] described below that have slightly different usage patterns.
+There are some [helper functions](#helpers) described below that have slightly different usage patterns.
### 4. Example ###
Assume we have a CouchDB instance with the following structure:
@@ -76,28 +68,38 @@ Assume we have a CouchDB instance with the following structure:
* _design/app
* doca
-After we have generated the API with defaults:
+We use the api thus:
- couchdb = require('YACA');
+ couchdb_factory = require('YACA');
- callback = function(error, response, body) {console.log(error, body)}
- // create new document
- couchdb.primarydb.put({uri:'doc2',json:{field1:'hello'}}, callback)
+ couchdb_factory( { db_url:'127.0.0.1:5984'
+ , admin :'administrator:password'
+ , update:true
+ }
+ , handle_api
+ )
+
+ function handle_api(error, couchdb) {
+ callback = function(error, response, body) {console.log(error, body)}
- // view existing document
- couchdb.primarydb.get('doc1', callback);
+ // create new document
+ couchdb.primarydb.put({uri:'doc2',json:{field1:'hello'}}, callback)
+
+ // view existing document
+ couchdb.primarydb.get('doc1', callback);
- // edit existing document
- couchdb.primarydb.put( { uri:'doc2'
- , json:{ field1:'goodbye'
- , _rev:'34_434c5f645'
- }
- }
- , callback
- )
-
- // view all docs from a view
- couchdb.secondarydb.app.views.by_date.get(callback)
+ // edit existing document
+ couchdb.primarydb.put( { uri:'doc2'
+ , json:{ field1:'goodbye'
+ , _rev:'34_434c5f645'
+ }
+ }
+ , callback
+ )
+
+ // view all docs from a view
+ couchdb.secondarydb.app.views.by_date.get(callback)
+ }
Hopefully this gives you a general idea of how the API works.
@@ -106,6 +108,17 @@ Hopefully this gives you a general idea of how the API works.
### Errors ###
When couchdb returns any code greater than 299, the api returns a javascript option parsed from the CouchDB error response with the added field 'statusCode,' with the http StatusCode.
+<a name='factory_options' />
+### Factory Options ###
+The couchdb_factory, takes an optional options hash. If no hash is given, the defaults will be used. The defaults usually work just fine. However it is important to use `update` when the database structure has changed.
+ * `db_url` - the url to your CouchDB instance. It defaults to `http://127.0.0.1:5984`. If privileges are needed to view design documents or the root instance, provide username and password as: `http(s)://username:password@host_name`
+ * `admin` - basic auth string, `USERNAME:PASSWORD`, where `USERNAME` is an admin username and `PASSWORD` is the admin password, if you wish to be able to access admin-restricted content through the api. *The username and password will be stored in plain text in the cache.* See [options] reference(#options) for more.
+ * `file` is the location of the cached database configuration. YACA caches thedatabase structure so it does not have to introspect the database every time it generates an api. If set to `false`, no cache will be created. If not specified, the cache will be placed in path/to/YACA/lib/couchdb.js.
+ * `update` controls introspection. If `update: true`, then the database will be introspected when an api is created even if a cache exists.
+
+You must create a new API whenever you add or remove a database, or you modify a design document.
+
+
<a name='options' />
### Options ###
YACA is derived from request, and so it supports most of the options supported by request, and a few more
@@ -117,7 +130,7 @@ The valid keys in the options object are:
* `body` - entity body for POST and PUT requests. Must be buffer or string.
* `json` - sets `body` but to JSON representation of value and adds `Content-type: application/json` header.
* `parse` - defaults to `true`, returned `body` will be a javascript object parsed from the JSON body.
-* `admin` - provide basic auth admin credentials if provided to api generator.
+* `admin` - provide basic auth admin credentials in the request to the database. Only provided if admin credentials were provided to couchdb_factory.
* `query` - hash object of query parameters. api uses JSON.stringify on any non-string hash values. Then uses querystring.stringify on the entire hash.
* `auth` - *not yet supported.* provide the given basic auth credentials. value must be a string: `username:password`.
* `onResponse` - *not yet supported.*
@@ -128,23 +141,23 @@ The valid keys in the options object are:
All of the following API calls conform are passed an options argument and a callback. The callback is then called with three arguments: an error, the CouchDB http response, and a javascript object parsed from the CouchDB response
#### CouchDB Root ####
-* couchdb(options, callback) - same as .get
+* couchdb(options, callback) - shortcut for .get
* couchdb.get(options, callback)
* couchdb.post(options, callback)
* couchdb.put(options, callback)
* couchdb.del(options, callback)
#### Database ####
-* couchdb.{{db}}(options, callback) - same as .get
+* couchdb.{{db}}(options, callback) - shortcut for .get
* couchdb.{{db}}.get(options, callback)
* couchdb.{{db}}.post(options, callback)
* couchdb.{{db}}.put(options, callback)
* couchdb.{{db}}.del(options, callback)
-Where {{db}} is the name of the database. if your database name is a protected word, the YACA generator will notify you, and {{db}} will be your database name prepended with '__' (two underscores).
+Where {{db}} is the name of the database. if your database name is a protected word, the YACA database_fatory will notify you, and {{db}} will be your database name prepended with '__' (two underscores).
#### Design Documents ####
-* couchdb.{{db}}.{{ddoc}}(options, callback) - same as .get
+* couchdb.{{db}}.{{ddoc}}(options, callback) - shortcut for .get
* couchdb.{{db}}.{{ddoc}}.get(options, callback)
* couchdb.{{db}}.{{ddoc}}.post(options, callback)
* couchdb.{{db}}.{{ddoc}}.put(options, callback)
@@ -157,26 +170,27 @@ Where {{db}} is as described above, and {{ddoc}} is the _id of your design doc s
* couchdb.{{db}}.{{ddoc}}.views.{{method}}.post(options, callback)
#### Shows ####
-* couchdb.{{db}}.{{ddoc}}.shows.{{method}}(options, callback) - same as .get
+* couchdb.{{db}}.{{ddoc}}.shows.{{method}}(options, callback) - shortcut for .get
* couchdb.{{db}}.{{ddoc}}.shows.{{method}}.get(options, callback)
* couchdb.{{db}}.{{ddoc}}.shows.{{method}}.post(options, callback)
#### Lists ####
-* couchdb.{{db}}.{{ddoc}}.lists.{{method}}.get(options, callback) - same as .get
+* couchdb.{{db}}.{{ddoc}}.lists.{{method}}.get(options, callback) - shortcut for .get
* couchdb.{{db}}.{{ddoc}}.lists.{{method}}.post(options, callback)
#### Updates ####
-* couchdb.{{db}}.{{ddoc}}.updates.{{method}}(options, callback) - same as .put
+* couchdb.{{db}}.{{ddoc}}.updates.{{method}}(options, callback) - shortcut for .put
* couchdb.{{db}}.{{ddoc}}.updates.{{method}}.put(options, callback)
* couchdb.{{db}}.{{ddoc}}.updates.{{method}}.post(options, callback)
#### Rewrites ####
-* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}(options, callback) - same as .get
+* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}(options, callback) - shortcut for .get
* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}.get(options, callback)
* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}.pust(options, callback)
* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}.post(options, callback)
* couchdb.{{db}}.{{ddoc}}.rewrites.{{method}}.del(options, callback)
+<a name='helpers' />
### Helper API ###
Anything you can do with the helper APIs you can do with the standard API. For example, you could get uuids in either of the two following ways:
View
159 bin/generate.js
@@ -1,159 +0,0 @@
-#!/usr/bin/env node
-
-request = require('request');
-querystring = require('querystring')
-fs = require('fs')
-url = require('url')
-async = require('async')
-cli = require('cli')
-path = require('path')
-
-path_rx = /{{path}}/g
-database_rx = /{{db}}/g
-ddoc_rx = /{{ddoc}}/g
-handler_rx = /{{handler}}/g
-method_rx = /{{method}}/g
-
-database_code = "\ncouchdb['{{db}}'] = db_factory('{{path}}')"
-
-ddoc_code = "\ncouchdb['{{db}}']['{{ddoc}}'] = db_factory('{{path}}')"
-
-handler_code = "\ncouchdb['{{db}}']['{{ddoc}}'].{{handler}} = {}"
-
-method_code = "\n\
-couchdb['{{db}}']['{{ddoc}}'].{{handler}}['{{method}}'] = method_factories['{{handler}}']('{{path}}')"
-
-handlers = {views:'_view/',shows:'_show/',lists:'_list/',updates:'_update/',rewrites:'_rewrite/'};
-
-function gen_interface(uri, filename, admin) {
- host = uri || 'http://127.0.0.1:5984/'
- host += (host.slice(-1) == '/') ? '' : '/' // ensure ends in slash
- uri = url.parse(host)
- filename = filename || path.join(__dirname, '/../lib/couchdb.js')
-
- // the results of parsing our db
- parsed_db = {_path:''};
-
- console.log('using host:', host)
- console.log('writing to file:', filename)
- if (admin) {
- console.log('making admin credentials available:', admin)
- }
- else {
- console.log('warning: no admin credentials provided')
- }
-
- var source = fs.readFileSync(path.join(__dirname,'/../lib/template.js'), 'utf8');
-
- function write_line(line) {
- source += line + '\n'
- }
-
-
- // generate and append root uris
- var ROOT = '"'
- ROOT += uri.protocol || 'http:'
- ROOT += '//'
- ROOT += uri.hostname
- ROOT += (uri.port) ? ':'+uri.port : ''
- ROOT += '/"'
-
- write_line('couchdb.ROOT = ' + ROOT)
-
- if (admin) {
- var ADMIN_ROOT ='"'
- ADMIN_ROOT += uri.protocol || 'http:'
- ADMIN_ROOT += '//'
- ADMIN_ROOT += admin+'@'
- ADMIN_ROOT += uri.hostname
- ADMIN_ROOT += (uri.port) ? ':'+uri.port : ''
- ADMIN_ROOT += '/"'
- write_line('couchdb.ADMIN_ROOT = ' + ADMIN_ROOT)
- }
-
- return request(host+'_all_dbs', handle_dbs);
-
- function handle_dbs(error, response, body) {
- body = JSON.parse(body);
- return async.forEach(body, parse_db, handle_db_complete)
- }
-
- function parse_db(db, db_callback) {
- // generate database api
- db_name = db
- if (['get', 'post', 'put', 'del', '_has_error', '_request_generator'].indexOf(db) >= 0) {
- db_name = '__'+db
- console.log(db, 'is a protected word. remapped to:', db_name)
- }
-
- db_path = db + '/';
- write_line(database_code.replace(database_rx, db_name).replace(path_rx, db_path))
-
- // get the design docs for this db
- query = querystring.stringify({startkey:'"_design/"',endkey:'"_design0"',include_docs:true})
- return request(host+db+'/_all_docs?'+query, handle_ddocs)
-
- function handle_ddocs(error, response, body) {
- db_name = response.request.path.split('/')[1];
- db_path = db_name + '/';
- db_name = (['get', 'post', 'put', 'del', '_has_error', '_request_generator'].indexOf(db) >= 0) ? '__'+db_name : db_name;
- body = JSON.parse(body);
- for (i in body.rows) {
- // generate ddoc api
- ddoc = body.rows[i].doc
- ddoc_name = ddoc._id.split('/')[1];
- if (['get', 'post', 'put', 'del', '_path'].indexOf(ddoc_name) >= 0) {
- console.log(ddoc_name, 'is a protected word. remapped to:', '__'+ddoc_name)
- ddoc_name = '__'+ddoc_name;
- }
-
- ddoc_path = db_path + ddoc._id + '/'
- write_line(ddoc_code.replace(ddoc_rx, ddoc_name).replace(database_rx, db_name).replace(path_rx, ddoc_path))
-
- // generate handler (eg views, updates) apis contained in ddoc
- for (handler_name in ddoc) {
- if (handler_name in handlers && JSON.stringify(ddoc[handler_name]) != JSON.stringify({})) {
- handler = ddoc[handler_name]
- handler_path = ddoc_path + handlers[handler_name];
- write_line(handler_code.replace(handler_rx, handler_name)
- .replace(ddoc_rx, ddoc_name)
- .replace(database_rx, db_name) )
-
- // put any method calls (eg _view/_by_id) in target handler
- for (method_name in handler) {
- method_path = handler_path + method_name + '/'
- write_line(method_code.replace(method_rx, method_name)
- .replace(ddoc_rx, ddoc_name)
- .replace(handler_rx, handler_name)
- .replace(database_rx, db_name)
- .replace(path_rx, method_path) )
- }
- }
- }
- }
- return db_callback();
- }
- }
-
- function handle_db_complete(error) {
- fs.writeFileSync(filename, source)
- console.log('Done')
- }
-}
-
-
-
-cli.parse( { 'file': ['f', 'filename for output code', 'path', path.join(__dirname,'../lib/couchdb.js')]
- , 'admin': ['a', 'admin basic auth in the form of: username:password', 'string']
- , 'db': ['d', 'db root url, include basic-auth admin privileges if needed to modify db.', 'url', 'http://127.0.0.1:5984']
- } )
-
-cli.main(function(args, options) {
- gen_interface(options.db, options.file, options.admin);
- })
-//if (!module.parent) {
-//console.log(process)
-// gen_interface(process.argv[2], process.argv[3], process.argv[4])
-//}
-
-
View
7 index.js
@@ -1,6 +1 @@
-try {
- module.exports = require('./lib/couchdb');
-}
-catch(err) {
- module.exports = require('./lib/template.js');
-}
+module.exports = require('./lib/api_factory.js');
View
219 lib/api_factory.js
@@ -0,0 +1,219 @@
+var Request = require('request');
+var querystring = require('querystring');
+var factory = require('./factory');
+var factory_factory = factory.factory;
+var proto_helper = factory.proto_helper;
+var constructor_helper = factory.constructor_helper;
+var child_constructor = factory.child_constructor;
+var fs = require('fs')
+var url = require('url')
+var async = require('async')
+var db_factory = require('./db_factory');
+var path = require('path')
+
+handlers = {views:'_view',shows:'_show',lists:'_list',updates:'_update',rewrites:'_rewrite'};
+
+// ** CouchDB Root **
+var proto_couchdb = proto_helper(['get', 'put', 'post', 'del'], 'get');
+
+// options:
+// file: name of cache file false if no cache - defaults to ./couchdb.json
+// db_url: url of couchdb - defaults to http://127.0.0.1:5984
+// update: update cache on init - defaults to false
+// admin: admin credentials
+proto_couchdb.constructor = function(options, callback){
+ if (!callback) {
+ callback = options;
+ options = {};
+ }
+
+ var that = this;
+ options = options || {};
+ var cache = {}
+
+ if (options.file !== false) {
+ options.file = options.file || path.join(__dirname, '/couchdb.js')
+ return fs.readFile(options.file, 'utf8', handle_file);
+ }
+ else {
+ return handle_file('no cache');
+ }
+
+ function handle_file(error, file) {
+ // if error, then no file, so set up defaults
+ // otherwise, use cache.
+ if (error) {
+ // set defaults
+ options.db_url = options.db_url || 'http://127.0.0.1:5984/';
+ generate_cache(options);
+ }
+ else {
+ // parse file and update with new options, if any;
+ cache = JSON.parse(file);
+ var changed = false;
+ if (options.update) {
+ changed = true;
+ delete options.update;
+ }
+ for (key in options) {
+ if (cache[key] != options[key]) {
+ cache[key] = options[key];
+ changed = true;
+ }
+ }
+ if (cache.update || changed) {
+ return generate_cache(cache);
+ }
+ else {
+ return generate_api(cache);
+ }
+ }
+ }
+
+ function generate_cache(options) {
+ // ensure url ends in slash
+ options.db_url += (options.db_url.slice(-1) == '/') ? '' : '/';
+ var uri = url.parse(options.db_url);
+
+ cache = options;
+ cache.children = {};
+
+ // generate and append root uris
+ cache.PATH = uri.protocol || 'http:'
+ cache.PATH += '//'
+ cache.PATH += uri.hostname
+ cache.PATH += (uri.port) ? ':'+uri.port : ''
+ cache.PATH += '/'
+
+ if (options.admin) {
+ cache.ADMIN_PATH = uri.protocol || 'http:'
+ cache.ADMIN_PATH += '//'
+ cache.ADMIN_PATH += admin+'@'
+ cache.ADMIN_PATH += uri.hostname
+ cache.ADMIN_PATH += (uri.port) ? ':'+uri.port : ''
+ cache.ADMIN_PATH += '/'
+ }
+ // add helper functions
+ constructor_helper.apply(that, [cache]);
+ that.get('_all_dbs', handle_dbs);
+
+ function handle_dbs(error, response, body) {
+ if (error) return callback(error);
+ return async.forEach(body, parse_db, write_cache)
+ }
+
+ // called on each database; adds db structure and path info to cache
+ function parse_db(db, db_callback) {
+ // insert paths
+ db_name = db
+ if (['get', 'post', 'put', 'del'].indexOf(db_name) >= 0) {
+ db_name = '__'+db_name
+ console.log(db, 'is a protected word. remapped to:', db_name)
+ }
+ console.log(db_name);
+ cache.children[db_name] = {children:{}};
+ var db_cache = cache.children[db_name];
+ add_paths(db_cache, cache, db);
+
+ // get the design docs for this db
+ query = {startkey:'"_design/"',endkey:'"_design0"',include_docs:true}
+ return that.get({uri:db+'/_all_docs',query:query}, handle_ddocs)
+
+ function handle_ddocs(error, response, body) {
+ db_name = response.request.path.split('/')[1];
+ if (['get', 'post', 'put', 'del'].indexOf(db_name) >= 0) {
+ db_name = '__'+db_name
+ }
+ db_cache = cache.children[db_name];
+
+ for (i in body.rows) {
+ // insert paths
+ ddoc = body.rows[i].doc
+ ddoc_name = ddoc._id.split('/')[1];
+ if (['get', 'post', 'put', 'del', '_path'].indexOf(ddoc_name) >= 0) {
+ console.log( ddoc_name
+ , 'is a protected word. remapped to:'
+ , '__'+ddoc_name
+ )
+ ddoc_name = '__'+ddoc_name;
+ }
+ db_cache.children[ddoc_name] = {children:{}};
+ ddoc_cache = db_cache.children[ddoc_name];
+ add_paths(ddoc_cache, db_cache, ddoc._id);
+
+ // handler (eg views, updates) paths
+ for (handler_name in ddoc) {
+ if ( handler_name in handlers
+ && JSON.stringify(ddoc[handler_name]) != JSON.stringify({})
+ ) {
+ handler = ddoc[handler_name]
+ ddoc_cache.children[handler_name] = {children:{}};
+ handler_cache = ddoc_cache.children[handler_name];
+ add_paths(handler_cache, ddoc_cache, handlers[handler_name]);
+
+ // put any method calls (eg _view/by_id) in target handler
+ for (method_name in handler) {
+ handler_cache.children[method_name] = {};
+ method_cache = handler_cache.children[method_name];
+ add_paths(method_cache, handler_cache, method_name);
+ }
+ }
+ }
+ }
+ return db_callback();
+ }
+ }
+
+ // called when all dbs are parsed. writes cache to disk.
+ function write_cache() {
+ if (cache.file) {
+ fs.writeFile(cache.file, JSON.stringify(cache), handle_write);
+ }
+
+ function handle_write(error) {
+ if (error) {
+ console.log( 'WARNING: unable to write cache to location:'
+ , cache.file
+ );
+ }
+ return generate_api();
+ }
+ }
+
+ // helper function - given a cache, parent_cache and path, append path
+ // to parent_cache paths and place in cache path.
+ function add_paths(cache, parent_cache, path) {
+ cache.PATH = parent_cache.PATH + path + '/';
+ if (parent_cache.ADMIN_PATH) {
+ cache.ADMIN_PATH = parent_cache.ADMIN_PATH + path + '/';
+ }
+ }
+ }
+
+ function generate_api() {
+ child_constructor.apply(that, [cache, db_factory]);
+ return callback();
+ }
+
+
+}
+
+
+proto_couchdb._uuids = function(count, callback) {
+ if(!callback) {
+ callback = count;
+ options = '';
+ }
+ else {
+ options = {query:{count:count}};
+ }
+ return couchdb._request(options, '_uuids', null, helper);
+
+ function helper(e,r,b) {
+ if (e) {return callback(e);}
+ return callback(null, b['uuids']);
+ }
+}
+
+var couchdb_factory = factory_factory(proto_couchdb)
+module.exports = couchdb_factory;
View
52 lib/db_factory.js
@@ -0,0 +1,52 @@
+var factory = require('./factory');
+var factory_factory = factory.factory_sync;
+var proto_helper = factory.proto_helper;
+var constructor_helper = factory.constructor_helper;
+var child_constructor = factory.child_constructor;
+
+// ** Methods **
+var proto_view = proto_helper(['get', 'post']);
+proto_view.call = function(query, callback) {
+ if (!callback) return couchdb._request('', this._path, null, query)
+ return couchdb._request({query:query}, this._path, null, callback)
+}
+var proto_show = proto_helper(['get', 'post'], 'get');
+var proto_list = proto_helper(['get', 'post'], 'get');
+var proto_update = proto_helper(['put', 'post'], 'put');
+var proto_rewrite = proto_helper(['get', 'put', 'post', 'del'], 'get');
+
+method_factories =
+ { views: factory_factory(proto_view)
+ , shows: factory_factory(proto_show)
+ , lists: factory_factory(proto_list)
+ , updates: factory_factory(proto_update)
+ , rewrites: factory_factory(proto_rewrite)
+ }
+
+// ** DDOC **
+var proto_ddoc = proto_helper(['get', 'put', 'post', 'del'], 'get');
+proto_ddoc.constructor = function(cache) {
+ constructor_helper.apply(this, [cache]);
+ for (handler_name in cache.children) {
+ this[handler_name] = {};
+ that = this[handler_name];
+ child_constructor.apply( that
+ , [ cache.children[handler_name]
+ , method_factories[handler_name]
+ ]
+ );
+ }
+}
+var ddoc_factory = factory_factory(proto_ddoc)
+
+// ** CouchDB root **
+var proto_db = proto_helper(['get', 'put', 'post', 'del'], 'get');
+proto_db.constructor = function(cache) {
+ constructor_helper.apply(this, [cache]);
+ child_constructor.apply(this, [cache, ddoc_factory]);
+}
+var db_factory = factory_factory(proto_db)
+
+
+
+module.exports = db_factory;
View
194 lib/factory.js
@@ -0,0 +1,194 @@
+// Derived from code kindly provided by Raynos (https://github.com/Raynos)
+var Request = require('request');
+var querystring = require('querystring');
+
+Object.extend = function(dest, source) {
+ Object.getOwnPropertyNames(source).forEach(function (key) {
+ dest[key] = source[key];
+ });
+};
+
+// this factory function takes a hash of methods and attributes for a
+// prototype object and creates a new factory for creating objects of
+// that type. if the call method is in the hash, it will be bound to the
+// object itself, making the object a callable function.
+var proto_factory_factory = function (proto_hash) {
+ var proto = Object.create(Function.prototype);
+ Object.extend(proto, proto_hash);
+ return function () {
+ var f = function () {
+ return proto.call.apply(f, arguments);
+ };
+ Object.keys(proto).forEach(function (key) {
+ if (key != 'call' && key !='constructor') {
+ f[key] = proto[key];
+ }
+ });
+ // set the callback and replace the passed callback with the internal
+ // callback;
+ var callback_index = ''+arguments.length-1;
+ var callback = arguments[callback_index];
+ arguments[callback_index] = handle_constructor_complete;
+ proto.constructor.apply(f, arguments);
+
+ function handle_constructor_complete(error) {
+ console.log('finished, returning instance');
+ if (error) return callback(error);
+ return callback(null, f);
+ }
+ }
+}
+
+var proto_factory_factory_sync = function (proto_hash) {
+ var proto = Object.create(Function.prototype);
+ Object.extend(proto, proto_hash);
+ return function () {
+ var f = function () {
+ return proto.call.apply(f, arguments);
+ };
+ Object.keys(proto).forEach(function (key) {
+ if (key != 'call' && key !='constructor') {
+ f[key] = proto[key];
+ }
+ });
+ proto.constructor.apply(f, arguments);
+ return f;
+ }
+}
+function init_helper(names) {
+ names.forEach(function(i) {that[i] = methods[i]});
+}
+
+// given a list of method names ['get','post','del','put'] generate an object
+// with those methods, plus a constructor. If an optional call string with a
+// method name is given, add a call method of that type.
+function proto_helper(names, call) {
+ var proto = {};
+ methods =
+ { get: function(options, callback) {
+ return this._request(options, null, callback)
+ }
+ , del: function(options, callback) {
+ return this._request(options, 'del', callback)
+ }
+ , put: function(options, callback) {
+ return this._request(options, 'put', callback)
+ }
+ , post: function(options, callback) {
+ return this._request(options, 'post', callback)
+ }
+ }
+
+ names.forEach(function(i) {proto[i] = methods[i]});
+ if (call) {proto.call = methods[call];}
+ proto.constructor = constructor_helper;
+ return proto;
+}
+
+/*
+cache =
+{ ADMIN_PATH:
+, PATH:
+
+*/
+function constructor_helper(cache) {
+ var ADMIN_PATH = cache.ADMIN_PATH;
+ var PATH = cache.PATH;
+
+ // private. has_error returns the error/couchdb error, if it exists or null.
+ var _has_error = function(error, response, body) {
+ // return connection error
+ if (error) {
+ return error
+ }
+ // return formatted couchdb error
+ else if (response.statusCode > 299) {
+ try {
+ body = JSON.parse(body)
+ }
+ catch (err) {}
+ body.statusCode = response.statusCode
+ return body;
+ }
+ // return no error
+ else {
+ return null
+ }
+ }
+
+ this._request = function(options, method, callback) {
+ console.log('generating request', options, PATH, method)
+ // convert options to a standardized form.
+ // if someone just gives a callback, the callback will be in the options
+ // variable, and callback will be empty. make call to db with empty path
+ if (typeof(options) == 'function' && !callback) {
+ callback = options;
+ options = {'uri':''};
+ }
+ // normalize to hash
+ else if (typeof(options) == 'string') {
+ options = {'uri':options}
+ }
+
+ if ('url' in options) {
+ options.uri = options.url
+ delete options.url
+ }
+
+ // make sure there is a uri
+ options.uri = (options.uri) ? options.uri : '';
+
+ // default to parsing JSON response
+ options.parse = (options.parse === false) ? false : true;
+
+ // create the uri
+ var uri;
+ uri = (options.admin && ADMIN_PATH) ? ADMIN_PATH : PATH;
+ uri += (options.uri == '/') ? '' : options.uri; // don't want a double slash
+ // append query, if any
+ if (options.query && JSON.stringify(options.query) != '{}') {
+ for (key in options.query) {
+ if (typeof(options.query[key]) != 'string') {
+ options.query[key] = JSON.stringify(options.query[key])
+ }
+ }
+ uri += '?' + querystring.stringify(options.query);
+ delete options.query;
+ }
+ options.uri = uri;
+
+ var request = (!method) ? Request : Request[method];
+ console.log('request options:', options)
+ return request(options, handle_response);
+
+ // return any couchdb errors as errors. handle body parsing unless specifically declined (options.parse=false)
+ function handle_response(error, response, body) {
+ error = _has_error(error, response, body);
+ if (error) {
+ return (callback(error, response, body));
+ }
+
+ if (options.parse && typeof(body) == 'string') {
+ try {body = JSON.parse(body)}
+ catch(err) {
+ return callback({'error':'json parse error','malformed JSON':body})
+ }
+ }
+ return callback(error, response, body);
+ }
+ }
+
+}
+
+function child_constructor(cache, child_factory) {
+ for (child_name in cache.children) {
+ var child_cache = cache.children[child_name];
+ this[child_name] = child_factory(child_cache);
+ }
+}
+
+exports.factory = proto_factory_factory
+exports.factory_sync = proto_factory_factory_sync
+exports.proto_helper = proto_helper
+exports.constructor_helper = constructor_helper
+exports.child_constructor = child_constructor
View
28 lib/factory_factory.js
@@ -1,28 +0,0 @@
-// Derived from code kindly provided by Raynos (https://github.com/Raynos)
-
-Object.extend = function(dest, source) {
- Object.getOwnPropertyNames(source).forEach(function (key) {
- dest[key] = source[key];
- });
-};
-
-// this factory function takes a hash of methods and attributes for a
-// prototype object and creates a new factory for creating objects of
-// that type. if the call method is in the hash, it will be bound to the
-// object itself, making the object a callable function.
-var proto_factory_factory = function (proto_hash) {
- var proto = Object.create(Function.prototype);
- Object.extend(proto, proto_hash);
- return function () {
- var f = function () {
- return f.call.apply(f, arguments);
- };
- Object.keys(proto).forEach(function (key) {
- f[key] = proto[key];
- });
- f.constructor.apply(f, arguments);
- return f;
- }
-}
-
-module.exports = proto_factory_factory
View
176 lib/template.js
@@ -1,176 +0,0 @@
-var Request = require('request');
-var querystring = require('querystring');
-var factory_factory = require('./factory_factory');
-
-function proto_helper(names, call) {
- var proto = {};
- methods =
- { get: function(options, callback) {
- return couchdb._request(options, this._path, null, callback)
- }
- , del: function(options, callback) {
- return couchdb._request(options, this._path, 'del', callback)
- }
- , put: function(options, callback) {
- return couchdb._request(options, this._path, 'put', callback)
- }
- , post: function(options, callback) {
- return couchdb._request(options, this._path, 'post', callback)
- }
- }
-
- names.forEach(function(i) {proto[i] = methods[i]});
- if (call) {proto.call = methods[call];}
- proto.constructor = function(path) {this._path = path;}
- return proto;
-}
-
-// ** CouchDB root **
-var proto_db = proto_helper(['get', 'put', 'post', 'del'], 'get');
-var db_factory = factory_factory(proto_db)
-
-// ** DDOC **
-var proto_ddoc = proto_helper(['get', 'put', 'post', 'del'], 'get');
-var ddoc_factory = factory_factory(proto_ddoc)
-
-// ** Handlers **
-var proto_view = proto_helper(['get', 'post']);
-proto_view.call = function(query, callback) {
- if (!callback) return couchdb._request('', this._path, null, query)
- return couchdb._request({query:query}, this._path, null, callback)
-}
-var proto_show = proto_helper(['get', 'post'], 'get');
-var proto_list = proto_helper(['get', 'post'], 'get');
-var proto_update = proto_helper(['put', 'post'], 'put');
-var proto_rewrite = proto_helper(['get', 'put', 'post', 'del'], 'get');
-
-method_factories =
- { views: factory_factory(proto_view)
- , shows: factory_factory(proto_show)
- , lists: factory_factory(proto_list)
- , updates: factory_factory(proto_update)
- , rewrites: factory_factory(proto_rewrite)
- }
-
-
-// ** CouchDB Root
-var couchdb = function(options, callback) {
- return couchdb._request(options, '', null, callback)
-}
-
-couchdb.get = function(options, callback) {
- return couchdb._request(options, '', null, callback)
-}
-
-couchdb.del = function(options, callback) {
- return couchdb._request(options, '', 'del', callback)
-}
-
-couchdb.post = function(options, callback) {
- return couchdb._request(options, '', 'post', callback)
-}
-
-couchdb.put = function(options, callback) {
- return couchdb._request(options, '', 'put', callback)
-}
-
-couchdb._uuids = function(count, callback) {
- if(!callback) {
- callback = count;
- options = '';
- }
- else {
- options = {query:{count:count}};
- }
- return couchdb._request(options, '_uuids', null, helper);
-
- function helper(e,r,b) {
- if (e) {return callback(e);}
- return callback(null, b['uuids']);
- }
-}
-
-// private. has_error returns the error/couchdb error, if it exists or null.
-couchdb._has_error = function(error, response, body) {
- // return connection error
- if (error) {
- return error
- }
- // return formatted couchdb error
- else if (response.statusCode > 299) {
- try {
- body = JSON.parse(body)
- }
- catch (err) {}
- body.statusCode = response.statusCode
- return body;
- }
- // return no error
- else {
- return null
- }
-}
-
-couchdb._request = function(options, path, method, callback) {
- console.log('generating request', options, path, method)
- // convert options to a standardized form.
- // if someone just gives a callback, the callback will be in the options
- // variable, and callback will be empty. make call to db with empty path
- if (typeof(options) == 'function' && !callback) {
- callback = options;
- options = {'uri':''};
- }
- // normalize to hash
- else if (typeof(options) == 'string') {
- options = {'uri':options}
- }
-
- if ('url' in options) {
- options.uri = options.url
- delete options.url
- }
-
- // make sure there is a uri
- options.uri = (options.uri) ? options.uri : '';
-
- // default to parsing JSON response
- options.parse = (options.parse === false) ? false : true;
-
- // create the uri
- var uri;
- uri = (options.admin && couchdb.ADMIN_ROOT) ? couchdb.ADMIN_ROOT : couchdb.ROOT;
- uri += path;
- uri += (options.uri == '/') ? '' : options.uri; // don't want a double slash
- // append query, if any
- if (options.query && JSON.stringify(options.query) != JSON.stringify({})) {
- for (key in options.query) {
- options.query[key] = (typeof(options.query[key]) == 'string') ? options.query[key]
- : JSON.stringify(options.query[key])
- }
- uri += '?' + querystring.stringify(options.query);
- delete options.query;
- }
- options.uri = uri;
-
- var request = (!method) ? Request : Request[method];
- console.log('request options:', options)
- return request(options, handle_response);
-
- // return any couchdb errors as errors. handle body parsing unless specifically declined (options.parse=false)
- function handle_response(error, response, body) {
- error = couchdb._has_error(error, response, body);
- if (error) {
- return (callback(error, response, body));
- }
-
- if (options.parse && typeof(body) == 'string') {
- try {body = JSON.parse(body)}
- catch(err) {
- return callback({'error':'json parse error','malformed JSON':body})
- }
- }
- return callback(error, response, body);
- }
-}
-
-module.exports = couchdb;
View
3  package.json
@@ -1,10 +1,9 @@
{ "name": "YACA"
, "description": "CouchDB api - generates a custom CouchDB api by introspecting a CouchDB instance"
-, "version": "0.1.1"
+, "version": "0.2.1"
, "directories": { "lib": "./lib"
, "bin": "./bin"
}
-, "bin": {"generate_couch_api": "./bin/generate.js"}
, "main": "index.js"
, "author": "David Greisen <dgreisen@gmail.com>"
, "repository": {"type":"git", "url":"http://github.com/dgreisen/YACA.git"}
Please sign in to comment.
Something went wrong with that request. Please try again.