You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2014/07/17 20:01:18 UTC

[02/12] query-server-node commit: updated refs/heads/master to dc16420

add couchjs-node from Jason


Project: http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/commit/1590a391
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/tree/1590a391
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/diff/1590a391

Branch: refs/heads/master
Commit: 1590a3918add41bf50510e8450d6ae4b21a3e42f
Parents: 
Author: Jan Lehnardt <ja...@apache.org>
Authored: Sat Sep 21 15:42:47 2013 +0200
Committer: Jan Lehnardt <ja...@apache.org>
Committed: Thu Oct 3 17:21:29 2013 +0200

----------------------------------------------------------------------
 LICENSE            | 202 ++++++++++++++++++++++++++++
 README.md          |  54 ++++++++
 checkout.sh        |  36 +++++
 cli.js             |  85 ++++++++++++
 console.js         |  65 +++++++++
 couchdb.js         |  29 ++++
 couchjs.js         | 116 ++++++++++++++++
 extra.js           | 346 ++++++++++++++++++++++++++++++++++++++++++++++++
 inspector.js       |  92 +++++++++++++
 package.json       |  30 +++++
 stream.js          | 107 +++++++++++++++
 test/experiment.js | 105 +++++++++++++++
 xml.js             |  23 ++++
 13 files changed, 1290 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c686e80
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+# CouchJS
+
+## Drop-in replacement JavaScript engine for Apache CouchDB
+
+CouchJS is a command-line Node.js program. It is 100% compatible with Apache CouchDB's built-in JavaScript system.
+
+By using CouchJS, you will get 100% CouchDB compatibility (the test suite completely passes) but your JavaScript environment is V8, or Node.js.
+
+CouchJS is available as an npm module.
+
+    $ npm install -g couchjs
+
+## Usage
+
+Install CouchDB. Install this package with npm. Confirm your `couchjs` install location.
+
+    $ which couchjs # Note, your path will be different from mine.
+    /home/jhs/node/bin/couchjs
+
+Look at the CouchDB config for the JavaScript query server.
+
+    $ curl http://localhost:5984/_config/query_servers/javascript
+    "/home/jhs/couchdb/bin/couchjs /home/jhs/couchdb/share/couchdb/server/main.js"
+
+Change that to this `couchjs`. **Leave the second argument the same.**
+
+    $ curl -X PUT http://localhost:5984/_config/query_servers/javascript \
+      -H content-type:application/json \
+      -d "\"`which couchjs` /home/jhs/couchdb/share/couchdb/server/main.js\""
+
+Done!
+
+## Idea
+
+JavaScript is decoupled from the CouchDB core. To do JavaScript stuff, CouchDB runs a normal Unix subprocess, `couchjs`. This subprocess is just a read-eval-print loop on standard i/o. CouchDB passes `couchjs` a file name, and *that file* contains the view server  implementation.
+
+This tool duplicates the "REPL" look and feel of `couchjs` and supports the exact same view server implementation.
+
+## Security
+
+I have no idea. I would not trust it for production use.
+
+## Log
+
+If you create a file, `/tmp/couchjs.log` then *couchjs* will output debugging messages there.
+
+## License
+
+Apache 2.0
+
+See the [Apache 2.0 license](named/blob/master/LICENSE).
+
+[tap]: https://github.com/isaacs/node-tap
+[def]: https://github.com/iriscouch/defaultable

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/checkout.sh
----------------------------------------------------------------------
diff --git a/checkout.sh b/checkout.sh
new file mode 100644
index 0000000..0076b51
--- /dev/null
+++ b/checkout.sh
@@ -0,0 +1,36 @@
+# Copyright 2011 Iris Couch
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+repo="$1"
+commit="$2"
+
+if [ -z "$repo" -o ! -d "$repo" ]; then
+  echo "Not a valid repo: $repo" >&2
+  exit 1
+fi
+if [ -z "$commit" ]; then
+  echo "Not a valid commit: $commit" >&2
+  exit 1
+fi
+
+echo "Clone $repo:$commit to $(pwd)"
+
+set -e
+
+git clone "$repo" .
+git checkout "$commit"
+
+if [ -z "$skip_npm_install" ]; then
+  npm install --production
+fi

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/cli.js
----------------------------------------------------------------------
diff --git a/cli.js b/cli.js
new file mode 100755
index 0000000..14a6d97
--- /dev/null
+++ b/cli.js
@@ -0,0 +1,85 @@
+#!/usr/bin/env node
+//
+// couchjs replacement
+//
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+var fs = require('fs')
+var util = require('util')
+var Fiber = require('fibers')
+var optimist = require('optimist')
+var child_process = require('child_process')
+
+var couchjs = require('./couchjs')
+var package_json = require('./package.json')
+var couchdb_extra = require('./extra')
+var LineStream = require('./stream')
+var inspector = require('./inspector')
+var log = require('./console').log
+
+
+var opts = optimist.boolean(['h', 'V', 'H'])
+                   .describe({ 'h': 'display a short help message and exit'
+                             , 'V': 'display version information and exit'
+                             , 'H': 'enable couchjs cURL bindings (not implemented)'
+                             })
+                   .boolean('extra')
+                   .describe('extra', 'Extra features for CouchDB, for os_daemons')
+                   .usage('$0 <path to main.js>')
+
+
+function main() {
+  if(opts.argv.extra)
+    return couchdb_extra()
+
+  var main_js = opts.argv._[0]
+  if(!main_js)
+    return console.error(opts.help())
+
+  log('couchjs/%s %s: %s', package_json.version, process.pid, main_js)
+  if(process.env.COUCHJS_DEBUG_PORT)
+    inspector(+process.env.COUCHJS_DEBUG_PORT)
+
+  fs.readFile(main_js, 'utf8', function(er, body) {
+    if(er)
+      throw er
+
+    var stdin = new LineStream.v2
+    stdin.on('readable', function() {
+      var buf = stdin.read()
+      if(buf)
+        couchjs.stdin(buf)
+    })
+    stdin.on('end', function() {
+      log('Terminate; connection to parent closed')
+      process.exit(0)
+    })
+
+    process.stdin.setEncoding('utf8')
+    process.stdin.pipe(stdin)
+
+    var main_func = Function(['print', 'readline', 'evalcx', 'gc', 'quit'], body)
+
+    log('Call main')
+    Fiber(function() { main_func(couchjs.print, couchjs.readline, couchjs.evalcx, couchjs.gc) }).run()
+  })
+
+  process.on('uncaughtException', function(er) {
+    log('Error:\n%s', er.stack)
+  })
+}
+
+if(require.main === module)
+  main()

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/console.js
----------------------------------------------------------------------
diff --git a/console.js b/console.js
new file mode 100644
index 0000000..2a0a17a
--- /dev/null
+++ b/console.js
@@ -0,0 +1,65 @@
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+var fs = require('fs')
+var util = require('util')
+
+
+module.exports = {}
+module.exports.log = noop
+module.exports.debug = noop
+module.exports.info = noop
+module.exports.warn = noop
+module.exports.error = noop
+
+var LOG_PATH = '/tmp/couchjs.log'
+  , stat = null
+  , LOG = null
+
+try {
+  stat = fs.statSync(LOG_PATH)
+} catch(er) {}
+
+if(stat) {
+  LOG = fs.createWriteStream(LOG_PATH, {'flags':'a'})
+
+  module.exports.log = log
+  module.exports.debug = log
+  module.exports.info = log
+  module.exports.warn = log
+  module.exports.error = log
+
+  process.on('exit', function() {
+    module.exports.log('Exit %d', process.pid)
+  })
+
+  process.on('uncaughtException', on_err)
+}
+
+function log() {
+  var str = util.format.apply(this, arguments)
+  LOG.write(str + '\n')
+}
+
+function on_err(er) {
+  module.exports.error('Uncaught error:\n%s', er.stack || er.message || JSON.stringify(er))
+
+  if(er.stack)
+    er = ['fatal', 'unknown_error', er.stack]
+
+  process.stdout.write(JSON.stringify(er) + '\n')
+  process.exit(1)
+}
+
+function noop() {}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/couchdb.js
----------------------------------------------------------------------
diff --git a/couchdb.js b/couchdb.js
new file mode 100755
index 0000000..d297112
--- /dev/null
+++ b/couchdb.js
@@ -0,0 +1,29 @@
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = handler
+
+var http = require('http')
+
+function handler(req, res) {
+  res.writeHead(200)
+  res.end('Hello: ' + req.url + '\n')
+}
+
+if(require.main === module) {
+  var http = require('http')
+  var server = http.createServer(handler)
+  server.listen(3000)
+  console.log('Listening on :3000')
+}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/couchjs.js
----------------------------------------------------------------------
diff --git a/couchjs.js b/couchjs.js
new file mode 100644
index 0000000..d95341d
--- /dev/null
+++ b/couchjs.js
@@ -0,0 +1,116 @@
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = { 'print'   : print
+                 , 'readline': readline
+                 , 'stdin'   : stdin
+                 , 'evalcx'  : evalcx
+                 , 'quit'    : quit
+                 , 'gc'      : gc
+                 }
+
+
+var vm = require('vm')
+var Fiber = require('fibers')
+
+var XML = require('./xml')
+var log = require('./console').log
+
+var INPUT = {'queue':[], 'waiting':null}
+
+Error.prototype.toSource = Error.prototype.toSource || toSource
+Error.prototype.toString = Error.prototype.toString || toSource
+Function.prototype.toSource = Function.prototype.toSource || toSource
+Function.prototype.toString = Function.prototype.toString || toSource
+
+
+function print(line) {
+  log('STDOUT %s: %s', process.pid, line)
+  process.stdout.write(line + '\n')
+
+  try {
+    line = JSON.parse(line)
+  } catch(er) { return }
+
+  if(line[0] == 'log')
+    log('LOG: %s', line[1])
+}
+
+function stdin(line) {
+  log('STDIN %s: %s', process.pid, line.trim())
+  if(INPUT.waiting)
+    INPUT.waiting.run(line)
+  else
+    INPUT.queue.push(line)
+}
+
+function readline() {
+  var line = INPUT.queue.shift()
+  if(line)
+    return line
+
+  INPUT.waiting = Fiber.current
+  line = Fiber.yield()
+  INPUT.waiting = null
+
+  return line
+}
+
+
+function evalcx(source, sandbox) {
+  sandbox = sandbox || {}
+  //log('evalcx in %j: %j', Object.keys(sandbox), source)
+
+  if(source == '')
+    return sandbox
+
+  // source might be "function(doc) { emit(doc._id, 1) }"
+  source = source.replace(/;+$/, '')
+
+  sandbox.XML = sandbox.XML || XML
+  source = '(' + source + ')'
+
+  try {
+    var id = Math.floor(Math.random() * 1000*1000)
+    var filename = '_couchdb:' + id + '.js'
+    var script = vm.createScript(source, filename)
+    var func = script.runInNewContext(sandbox)
+  } catch (er) {
+    log('Error making code: %s', er.stack)
+    return sandbox
+  }
+
+  return func
+}
+
+function quit(code) {
+  code = code || 1
+  if(code < 0)
+    code = -code
+
+  process.exit(code)
+}
+
+function gc() { }
+
+
+function toSource() {
+  if(typeof this == 'function')
+    return '' + this
+
+  if(this instanceof Error)
+    return this.stack
+
+  return util.inspect(this)
+}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/extra.js
----------------------------------------------------------------------
diff --git a/extra.js b/extra.js
new file mode 100755
index 0000000..2df0168
--- /dev/null
+++ b/extra.js
@@ -0,0 +1,346 @@
+#!/usr/bin/env node
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = main
+
+var os = require('os')
+var fs = require('fs')
+var URL = require('url')
+var util = require('util')
+var http = require('http')
+var async = require('async')
+var mkdirp = require('mkdirp')
+var request = require('request')
+var optimist = require('optimist')
+var pushover = require('pushover')
+var child_process = require('child_process')
+
+var console = require('./console')
+var VER = require('./package.json').version
+
+var couch = { 'log': mk_couch_log('info')
+            , 'warn' : mk_couch_log('warn')
+            , 'error': mk_couch_log('error')
+            , 'debug': mk_couch_log('debug')
+            }
+
+
+var opts = optimist.usage('$0')
+
+var COUCH = null
+var GIT_DIR = null
+var COUCH_PASSWORD = null
+var GIT_PORT = null
+var APPLICATION = null
+
+function main() {
+  if(opts.argv.help)
+    return console.log(opts.help())
+
+  console.log('Extra CouchDB daemon: %s', process.pid)
+  couch.debug('CouchDB daemon %s: %s', VER, process.pid)
+
+  var env = {}
+  for (var k in process.env) {
+    var match = k.match(/^_couchdb_app_(.*)$/)
+    if(match)
+      env[match[1]] = process.env[k]
+  }
+
+  for (k in env)
+    couch.debug('  %s = %s', k, env[k])
+
+  if(env.port && env.password && env.couch && env.dir)
+    return git(env)
+
+  setInterval(function() {
+    console.log('Still here')
+    couch.log('Still in couch')
+  }, 60000)
+}
+
+function git(env) {
+  GIT_PORT = +env.port
+  COUCH = env.couch
+  COUCH_DIR = util.format('%s/couchjs-%s', env.dir, VER)
+  COUCH_PASSWORD = env.password
+
+  //var couch_url = util.format('http://_nodejs:%s@127.0.0.1:%d', password, couch_port)
+  //couch.log('couch url %j', couch_url)
+
+  auth('_nodejs', COUCH_PASSWORD, function(er, userCtx) {
+    if(er)
+      throw er
+
+    var roles = userCtx.roles || []
+    if(userCtx.name != '_nodejs' || !~roles.indexOf('_admin'))
+      throw new Error('Not admin: ' + JSON.stringify(res.body.userCtx))
+
+    var repos = pushover(COUCH_DIR)
+    repos.on('push', function(push) {
+      couch.log('Push %s/%s: %s', push.repo, push.commit, push.branch)
+      push.accept()
+      //couch.log('Response: %j', Object.keys(push.response))
+      push.response.on('finish', function() {
+        //couch.log('Finished!')
+        publish(push)
+      })
+    })
+
+    repos.on('fetch', function(fetch) {
+      couch.log('fetch %j', fetch.commit)
+      fetch.accept()
+    })
+
+    var server = http.createServer(function(req, res) {
+      if(! req.url.match(/^\/_nodejs\/_git(\/|$)/))
+        return handle_http(req, res)
+
+      req.pause()
+      auth_req(req, function(er, userCtx) {
+        if(er && er.statusCode) {
+          res.writeHead(er.statusCode, er.headers)
+          return res.end(er.body)
+        }
+
+        if(er) {
+          couch.log('Bad req %s: %s', req.url, er.message)
+          return res.end()
+        }
+
+        var roles = userCtx.roles || []
+        if(!~ roles.indexOf('_admin')) {
+          couch.log('Not admin: %s, %j', req.url, userCtx)
+          res.writeHead(401, 'Unauthorized', {'content-type':'application/json'})
+          return res.end('{"error":"not_authorized"}\n')
+        }
+
+        //couch.log('Handle Git: %j', req.url)
+        repos.handle(req, res)
+        req.resume()
+      })
+    })
+
+    server.listen(GIT_PORT)
+  })
+}
+
+function handle_http(req, res) {
+  if(! APPLICATION) {
+    var headers = { 'content-type': 'application/json'
+                  , 'server': 'NodeJS-CouchDB/'+VER
+                  }
+    res.writeHead(200, 'OK', headers)
+    var body = {'ok':true}
+    return res.end(JSON.stringify(body) + '\n')
+  }
+
+  // Clean up the vhost changes.
+  var vhost_path = req.headers['x-couchdb-vhost-path']
+  if(vhost_path) {
+    req.url = vhost_path
+    delete req.headers['x-couchdb-vhost-path']
+  }
+
+  APPLICATION(req, res)
+}
+
+function auth(user, pass, callback) {
+  if(!COUCH)
+    return process.nextTick(function() { callback(new Error('No _couchdb_port')) })
+
+  var url = COUCH + '/_session'
+  if(user || pass) {
+    url = URL.parse(url)
+    url.auth = util.format('%s:%s', user || '', pass || '')
+    url = URL.format(url)
+  }
+
+  //couch.log('auth: %j', url)
+  request({'url':url, 'json':true}, function(er, res) {
+    //couch.log('auth result: %j', res.body)
+    if(er)
+      return callback(er)
+
+    if(res.statusCode != 200) {
+      er = new Error('Bad status '+res.statusCode+' for auth: ' + res.body)
+      er.statusCode = res.statusCode
+      er.body = JSON.stringify(res.body) + '\n'
+      er.headers = res.headers
+      return callback(er)
+    }
+
+    return callback(null, res.body.userCtx)
+  })
+}
+
+function auth_req(req, callback) {
+  var headers = req.headers || {}
+  var auth_str = req.headers.authorization || ''
+
+  var match = auth_str.match(/^Basic (.+)$/)
+  if(!match)
+    return auth(null, null, callback)
+
+  try {
+    auth_str = new Buffer(match[1], 'base64').toString()
+    match = auth_str.match(/^([^:]+):(.+)$/)
+  } catch (er) {
+    return callback(er)
+  }
+
+  if(!match)
+    return callback(new Error('Bad auth string: ' + auth_str))
+
+  auth(match[1], match[2], callback)
+}
+
+
+function publish(push) {
+  var script = __dirname + '/checkout.sh'
+  var repo = COUCH_DIR + '/' + push.repo
+
+  var id = Math.floor(Math.random() * 1000 * 1000)
+  var work = util.format('%s/%s/%s', COUCH_DIR, push.commit, id)
+
+  mkdirp(work, function(er) {
+    if(er) {
+      couch.error('Failed to make working dir: %s', work)
+      throw er
+    }
+
+    checkout()
+  })
+
+  function checkout() {
+    var args = [script, repo, push.commit]
+    var opts = { 'cwd':work, 'stdio':'pipe' }
+
+    var child = child_process.spawn('bash', args, opts)
+
+    var output = []
+
+    child.stdout.on('data', function(x) {
+      var msg = util.format('OUT %s', x.toString().trim())
+      couch.debug(msg)
+      output.push(msg)
+    })
+    child.stderr.on('data', function(x) {
+      var msg = util.format('ERR %s', x.toString().trim())
+      couch.debug(msg)
+      output.push(msg)
+    })
+
+    child.on('exit', function(code) {
+      if(code !== 0) {
+        couch.error('Bad checkout: %d', code)
+        output.forEach(function(line) {
+          couch.error(line)
+        })
+
+        throw new Error('Bad checkout')
+      }
+
+      couch.log('Checked out push: %s', work)
+      fs.readFile(work+'/package.json', 'utf8', function(er, body) {
+        if(er)
+          throw er
+
+        body = JSON.parse(body)
+        if(!body.couchdb)
+          return couch.warn('No "couchdb" value in pushed package.json')
+
+        run_app(work, body)
+      })
+    })
+  }
+}
+
+
+function run_app(work_dir, pkg) {
+  var vhosts = []
+    , main = null
+
+  if(typeof pkg.couchdb == 'string')
+    main = pkg.couchdb
+  else {
+    vhosts = pkg.couchdb.vhosts || []
+    main = pkg.couchdb.main
+  }
+
+  couch.log('Run app %s: %j', main, vhosts)
+
+  var mod_path = util.format('%s/%s', work_dir, main)
+  try {
+    var ok = require.resolve(mod_path)
+  } catch (er) {
+    return couch.error('Bad module path: %s', mod_path)
+  }
+
+  couch_mod = require(mod_path)
+  APPLICATION = couch_mod
+  couch.log('Installed CouchDB application')
+
+  return async.forEach(vhosts, set_vhost, vhosts_set)
+
+  function set_vhost(vhost, to_async) {
+    var couch_url = URL.parse(COUCH)
+    couch_url.auth = '_nodejs:' + COUCH_PASSWORD
+    couch_url = URL.format(couch_url)
+    couch.log('couch_url: %j', couch_url)
+
+    var url = couch_url + '_config/vhosts/' + vhost
+    var body = '/_nodejs'
+    request.put({'url':url, 'json':body}, function(er, res) {
+      if(er)
+        return to_async(er)
+      if(res.statusCode != 200)
+        return to_async(new Error('Bad response '+res.statusCode+' to vhost: ' + vhost))
+
+      couch.log('Set vhost: %s', vhost)
+      return to_async()
+    })
+  }
+
+  function vhosts_set(er) {
+    if(er)
+      throw er
+
+    couch.log('Set %d vhosts for CouchDB application', vhosts.length)
+  }
+}
+
+
+//
+// Utilities
+//
+
+function mk_couch_log(level) {
+  if(level == 'warn')
+    level = 'error'
+
+  return logger
+
+  function logger() {
+    var str = util.format.apply(util, arguments)
+    var msg = ['log', str, {'level':level}]
+    msg = JSON.stringify(msg)
+    process.stdout.write(msg + '\n')
+  }
+}
+
+
+if(require.main === module)
+  main()

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/inspector.js
----------------------------------------------------------------------
diff --git a/inspector.js b/inspector.js
new file mode 100755
index 0000000..efa9856
--- /dev/null
+++ b/inspector.js
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+//
+// couchjs replacement
+//
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = start
+
+if(require.main === module)
+  main()
+
+
+var fs = require('fs')
+var util = require('util')
+var child_process = require('child_process')
+
+var log = require('./console').log
+
+function start(debugPort) {
+  if(!debugPort || typeof debugPort != 'number')
+    throw new Error('Need a listen debugPort')
+  var webPort = debugPort + 1
+
+  var cmd = __filename
+  var args = [debugPort, webPort]
+  var opts =
+    { 'cwd': __dirname
+    , 'stdio': 'pipe'
+    , 'detached': false
+    }
+
+  log('Start inspector: %s %j %j', cmd, args, opts)
+  var inspector = child_process.spawn(cmd, args, opts)
+  watch_inspector(inspector)
+
+  log('Enable remote debug pid=%d port=%d', process.pid, debugPort)
+  process.debugPort = debugPort
+  process.kill(process.pid, 'SIGUSR1')
+}
+
+function watch_inspector(child) {
+  child.stderr.on('data', function(body) {
+    log('Inspector STDERR: %s', body)
+  })
+  child.stdout.on('data', function(body) {
+    log('Inspector STDOUT: %s', body)
+  })
+
+  child.on('exit', function(code, signal) {
+    log('Inspector exited %d signal=%j', code, signal)
+    process.exit(code)
+  })
+
+  process.on('exit', function() {
+    log('Kill inspector upon exit: %d', child.pid)
+    process.kill(child.pid, 'SIGTERM')
+  })
+}
+
+
+function main() {
+  var debugPort = +process.argv[2]
+  var webPort = +process.argv[3]
+
+  if(!debugPort || !webPort)
+    throw new Error('Bad arguments: need debugPort and webPort')
+
+  console.log('Start inspector debugPort=%j webPort=%j', debugPort, webPort)
+  var DebugServer = require('node-inspector/lib/debug-server')
+  var server = new DebugServer
+  server.on('close', function() {
+    console.log('Server closed')
+    process.exit(0)
+  })
+
+  server.start({'webPort':webPort, 'debugPort':debugPort})
+  process.on('uncaughtException', function(er) {
+    console.log('Error:\n%s', er.stack)
+  })
+}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..665888c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{ "name": "couchjs"
+, "description": "Drop-in replacement for CouchDB JavaScript view server"
+, "keywords": [ "couchdb", "couchjs" ]
+, "version": "0.3.2"
+, "author": "Jason Smith <jh...@iriscouch.com> (http://www.iriscouch.com)"
+, "repository": { "type":"git", "url":"https://github.com/iriscouch/couchjs" }
+
+, "engines": { "node": ">= 0.8" }
+, "main": "./couchjs.js"
+, "bin": {"couchjs-node":"./cli.js", "couchjs-extra":"./extra.js"}
+
+, "couchdb": { "main":"./couchdb.js"
+             , "vhosts": ["127.0.0.1.xip.io"]
+             }
+
+, "bundledDependencies": ["node-inspector"]
+
+, "dependencies": { "optimist": "~0.3.4"
+                  , "async"   : "~0.2.5"
+                  , "mkdirp"  : "~0.3.4"
+                  , "fibers"  : "~1.0.0"
+                  , "request" : "~2.9.203"
+                  , "pushover": "~1.2.1"
+                  , "defaultable": "~0.7.2"
+                  , "node-inspector": "git://github.com/iriscouch/node-inspector#couchjs"
+                  }
+
+, "devDependencies": { "tap": "~0.2.5"
+                     }
+}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/stream.js
----------------------------------------------------------------------
diff --git a/stream.js b/stream.js
new file mode 100644
index 0000000..90deda1
--- /dev/null
+++ b/stream.js
@@ -0,0 +1,107 @@
+// Text line stream
+//
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = LineStream
+module.exports.v2 = LineStream2
+
+var stream = require('stream')
+var util = require('util')
+
+
+util.inherits(LineStream2, stream.Transform)
+function LineStream2 () {
+  if(! (this instanceof LineStream2))
+    return new LineStream2
+
+  stream.Transform.call(this)
+  this.setEncoding('utf8')
+}
+
+LineStream2.prototype._transform = function(message, encoding, done) {
+  var self = this
+
+  message = message.toString(encoding)
+  var lines = message.split(/\n/)
+
+  // If the data ends in "\n" this will be ""; otherwise the final partial line.
+  var remainder = lines.pop()
+  if(remainder)
+    this.unshift(remainder)
+
+  lines.forEach(function(line) {
+    self.push(line)
+  })
+
+  done()
+}
+
+util.inherits(LineStream, stream)
+function LineStream () {
+  var self = this
+  stream.call(self)
+
+  self.readable = true
+  self.writable = true
+
+  self.buffer = ''
+  self.downstream = null
+
+  self.on('pipe', function(upstream) {
+    upstream.on('end', function(data, encoding) {
+      self.emit('end', data, encoding)
+    })
+  })
+}
+
+
+LineStream.prototype.write = function(data, encoding) {
+  var self = this
+
+  data = data || ''
+  if(typeof data != 'string')
+    return self.error(new Error('Data was not a string: ' + util.inspect(data)))
+
+  self.buffer += data
+  var lines = self.buffer.split(/\n/)
+  self.buffer = lines.pop() // If the data ended in "\n" this will be ""; otherwise the final partial line.
+
+  lines.forEach(function(line) {
+    self.emit('data', line)
+  })
+}
+
+
+LineStream.prototype.end = function(data, encoding) {
+  var self = this
+
+  self.is_ending = true
+  self.writable = false
+
+  // Always call write, even with no data, so it can fire the "end" event.
+  self.write(data)
+}
+
+
+LineStream.prototype.error = function(er) {
+  var self = this
+
+  self.readable = false
+  self.writable = false
+  self.emit('error', er)
+
+  // The write() method sometimes returns this value, so if there was an error, make write() return false.
+  return false
+}

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/test/experiment.js
----------------------------------------------------------------------
diff --git a/test/experiment.js b/test/experiment.js
new file mode 100644
index 0000000..54a5ab4
--- /dev/null
+++ b/test/experiment.js
@@ -0,0 +1,105 @@
+var vm = require('vm')
+var util = require('util')
+
+var STATE = 'wait'
+  , v = 'vm'
+
+function main() {
+  process.debugPort = 5859
+  process.kill(process.pid, 'SIGUSR1')
+
+  setTimeout(function() { stuff(0) }, 1000)
+}
+
+function stuff(count) {
+  console.log('Doing stuff: %d', count)
+  //debugger
+  STATE = 'vm'
+  console.log('More stuff: %d', count)
+  if(STATE == 'done')
+    console.log('Done')
+  else if(STATE == 'code')
+    setTimeout(code, 1000)
+  else if(STATE == 'eval')
+    test_eval()
+  else if(STATE == 'vm')
+    test_vm()
+  else if(STATE == 'wait')
+    setTimeout(function() { stuff(count+1) }, 1000)
+  else
+    throw new Error('Unknown state: ' + STATE)
+}
+
+function code() {
+  var code =
+    [ 'var foo = "in the code"'
+    , 'console.log("This is some code")'
+    , 'debugger'
+    , 'console.log("foo = " + foo)'
+    ].join('\n')
+
+  var runner = Function([], code)
+  console.log('Run runner in 1s')
+  setTimeout(run_runner, 1000)
+
+  function run_runner() {
+    console.log('About to run runner')
+    debugger
+    runner()
+    console.log('Runner done')
+  }
+}
+
+function test_eval() {
+  console.log('Test eval in 1s')
+  setTimeout(run_eval, 1000)
+
+  var code =
+    [ 'var foo = "in eval"'
+    , 'console.log("This is eval")'
+    , 'debugger'
+    , 'console.log("foo = " + foo)'
+    ].join('\n')
+
+  function run_eval() {
+    console.log('Run eval now')
+    debugger
+    eval(code)
+  }
+}
+
+function test_vm() {
+  console.log('Test vm')
+
+  var code =
+    [ 'var i = 10'
+    , 'setTimeout(hello, 1000)'
+    , ''
+    , 'function hello() {'
+    , '  debugger'
+    , '  console.log("Hello: " + i)'
+    , '  if(--i)'
+    , '    setTimeout(hello, 1000)'
+    , '}'
+    ].join('\n')
+
+  console.log('Run vm now')
+  var filename = '_couchdb:code.js'
+
+  var sandbox = {}
+    , ok = ['console', 'setTimeout']
+
+  ok.forEach(function(key) {
+    sandbox[key] = global[key]
+  })
+
+  var ctx = vm.createContext(sandbox)
+  var script = vm.createScript(code, filename)
+
+  var r = script.runInNewContext(sandbox)
+  console.log('Result:\n%s', util.inspect(r, false, 10))
+  return r
+}
+
+if(require.main === module)
+  main()

http://git-wip-us.apache.org/repos/asf/couchdb-query-server-node/blob/1590a391/xml.js
----------------------------------------------------------------------
diff --git a/xml.js b/xml.js
new file mode 100644
index 0000000..c81525b
--- /dev/null
+++ b/xml.js
@@ -0,0 +1,23 @@
+// Copyright 2011 Iris Couch
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+module.exports = XML
+
+function XML () {
+  this.foo = 'bar'
+}
+
+XML.prototype.toXMLString = function() {
+  return '<xml>\n  <title>test</title>\n</xml>'
+}