├── .gitignore ├── LICENSE ├── README.md ├── checkout.sh ├── cli.js ├── console.js ├── couchdb.js ├── couchjs.js ├── extra.js ├── inspector.js ├── package.json ├── stream.js ├── test └── experiment.js └── xml.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CouchJS 2 | 3 | ## Drop-in replacement JavaScript engine for Apache CouchDB 4 | 5 | CouchJS is a command-line Node.js program. It is 100% compatible with Apache CouchDB's built-in JavaScript system. 6 | 7 | By using CouchJS, you will get 100% CouchDB compatibility (the test suite completely passes) but your JavaScript environment is V8, or Node.js. 8 | 9 | CouchJS is available as an npm module. 10 | 11 | $ npm install -g couchjs 12 | 13 | ## Usage 14 | 15 | Install CouchDB. Install this package with npm. Confirm your `couchjs` install location. 16 | 17 | $ which couchjs # Note, your path will be different from mine. 18 | /home/jhs/node/bin/couchjs 19 | 20 | Look at the CouchDB config for the JavaScript query server. 21 | 22 | $ curl http://localhost:5984/_config/query_servers/javascript 23 | "/home/jhs/couchdb/bin/couchjs /home/jhs/couchdb/share/couchdb/server/main.js" 24 | 25 | Change that to this `couchjs`. **Leave the second argument the same.** 26 | 27 | $ curl -X PUT http://localhost:5984/_config/query_servers/javascript \ 28 | -H content-type:application/json \ 29 | -d "\"`which couchjs` /home/jhs/couchdb/share/couchdb/server/main.js\"" 30 | 31 | Done! 32 | 33 | ## Idea 34 | 35 | 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. 36 | 37 | This tool duplicates the "REPL" look and feel of `couchjs` and supports the exact same view server implementation. 38 | 39 | ## Security 40 | 41 | I have no idea. I would not trust it for production use. 42 | 43 | ## Log 44 | 45 | If you create a file, `/tmp/couchjs.log` then *couchjs* will output debugging messages there. 46 | 47 | ## License 48 | 49 | Apache 2.0 50 | 51 | See the [Apache 2.0 license](named/blob/master/LICENSE). 52 | 53 | [tap]: https://github.com/isaacs/node-tap 54 | [def]: https://github.com/iriscouch/defaultable 55 | -------------------------------------------------------------------------------- /checkout.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Iris Couch 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | repo="$1" 16 | commit="$2" 17 | 18 | if [ -z "$repo" -o ! -d "$repo" ]; then 19 | echo "Not a valid repo: $repo" >&2 20 | exit 1 21 | fi 22 | if [ -z "$commit" ]; then 23 | echo "Not a valid commit: $commit" >&2 24 | exit 1 25 | fi 26 | 27 | echo "Clone $repo:$commit to $(pwd)" 28 | 29 | set -e 30 | 31 | git clone "$repo" . 32 | git checkout "$commit" 33 | 34 | if [ -z "$skip_npm_install" ]; then 35 | npm install --production 36 | fi 37 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // 3 | // couchjs replacement 4 | // 5 | // Copyright 2011 Iris Couch 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | var fs = require('fs') 20 | var util = require('util') 21 | var Fiber = require('fibers') 22 | var optimist = require('optimist') 23 | var child_process = require('child_process') 24 | 25 | var couchjs = require('./couchjs') 26 | var package_json = require('./package.json') 27 | var couchdb_extra = require('./extra') 28 | var LineStream = require('./stream') 29 | var inspector = require('./inspector') 30 | var log = require('./console').log 31 | 32 | 33 | var opts = optimist.boolean(['h', 'V', 'H']) 34 | .describe({ 'h': 'display a short help message and exit' 35 | , 'V': 'display version information and exit' 36 | , 'H': 'enable couchjs cURL bindings (not implemented)' 37 | }) 38 | .boolean('extra') 39 | .describe('extra', 'Extra features for CouchDB, for os_daemons') 40 | .usage('$0 ') 41 | 42 | 43 | function main() { 44 | if(opts.argv.extra) 45 | return couchdb_extra() 46 | 47 | var main_js = opts.argv._[0] 48 | if(!main_js) 49 | return console.error(opts.help()) 50 | 51 | log('couchjs/%s %s: %s', package_json.version, process.pid, main_js) 52 | if(process.env.COUCHJS_DEBUG_PORT) 53 | inspector(+process.env.COUCHJS_DEBUG_PORT) 54 | 55 | fs.readFile(main_js, 'utf8', function(er, body) { 56 | if(er) 57 | throw er 58 | 59 | var stdin = new LineStream.v2 60 | stdin.on('readable', function() { 61 | var buf = stdin.read() 62 | if(buf) 63 | couchjs.stdin(buf) 64 | }) 65 | stdin.on('end', function() { 66 | log('Terminate; connection to parent closed') 67 | process.exit(0) 68 | }) 69 | 70 | process.stdin.setEncoding('utf8') 71 | process.stdin.pipe(stdin) 72 | 73 | var main_func = Function(['print', 'readline', 'evalcx', 'gc', 'quit'], body) 74 | 75 | log('Call main') 76 | Fiber(function() { main_func(couchjs.print, couchjs.readline, couchjs.evalcx, couchjs.gc) }).run() 77 | }) 78 | 79 | process.on('uncaughtException', function(er) { 80 | log('Error:\n%s', er.stack) 81 | }) 82 | } 83 | 84 | if(require.main === module) 85 | main() 86 | -------------------------------------------------------------------------------- /console.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Iris Couch 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var fs = require('fs') 16 | var util = require('util') 17 | 18 | 19 | module.exports = {} 20 | module.exports.log = noop 21 | module.exports.debug = noop 22 | module.exports.info = noop 23 | module.exports.warn = noop 24 | module.exports.error = noop 25 | 26 | var LOG_PATH = '/tmp/couchjs.log' 27 | , stat = null 28 | , LOG = null 29 | 30 | try { 31 | stat = fs.statSync(LOG_PATH) 32 | } catch(er) {} 33 | 34 | if(stat) { 35 | LOG = fs.createWriteStream(LOG_PATH, {'flags':'a'}) 36 | 37 | module.exports.log = log 38 | module.exports.debug = log 39 | module.exports.info = log 40 | module.exports.warn = log 41 | module.exports.error = log 42 | 43 | process.on('exit', function() { 44 | module.exports.log('Exit %d', process.pid) 45 | }) 46 | 47 | process.on('uncaughtException', on_err) 48 | } 49 | 50 | function log() { 51 | var str = util.format.apply(this, arguments) 52 | LOG.write(str + '\n') 53 | } 54 | 55 | function on_err(er) { 56 | module.exports.error('Uncaught error:\n%s', er.stack || er.message || JSON.stringify(er)) 57 | 58 | if(er.stack) 59 | er = ['fatal', 'unknown_error', er.stack] 60 | 61 | process.stdout.write(JSON.stringify(er) + '\n') 62 | process.exit(1) 63 | } 64 | 65 | function noop() {} 66 | -------------------------------------------------------------------------------- /couchdb.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Iris Couch 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = handler 16 | 17 | var http = require('http') 18 | 19 | function handler(req, res) { 20 | res.writeHead(200) 21 | res.end('Hello: ' + req.url + '\n') 22 | } 23 | 24 | if(require.main === module) { 25 | var http = require('http') 26 | var server = http.createServer(handler) 27 | server.listen(3000) 28 | console.log('Listening on :3000') 29 | } 30 | -------------------------------------------------------------------------------- /couchjs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Iris Couch 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 'print' : print 16 | , 'readline': readline 17 | , 'stdin' : stdin 18 | , 'evalcx' : evalcx 19 | , 'quit' : quit 20 | , 'gc' : gc 21 | } 22 | 23 | 24 | var vm = require('vm') 25 | var Fiber = require('fibers') 26 | 27 | var XML = require('./xml') 28 | var log = require('./console').log 29 | 30 | var INPUT = {'queue':[], 'waiting':null} 31 | 32 | Error.prototype.toSource = Error.prototype.toSource || toSource 33 | Error.prototype.toString = Error.prototype.toString || toSource 34 | Function.prototype.toSource = Function.prototype.toSource || toSource 35 | Function.prototype.toString = Function.prototype.toString || toSource 36 | 37 | 38 | function print(line) { 39 | log('STDOUT %s: %s', process.pid, line) 40 | process.stdout.write(line + '\n') 41 | 42 | try { 43 | line = JSON.parse(line) 44 | } catch(er) { return } 45 | 46 | if(line[0] == 'log') 47 | log('LOG: %s', line[1]) 48 | } 49 | 50 | function stdin(line) { 51 | log('STDIN %s: %s', process.pid, line.trim()) 52 | if(INPUT.waiting) 53 | INPUT.waiting.run(line) 54 | else 55 | INPUT.queue.push(line) 56 | } 57 | 58 | function readline() { 59 | var line = INPUT.queue.shift() 60 | if(line) 61 | return line 62 | 63 | INPUT.waiting = Fiber.current 64 | line = Fiber.yield() 65 | INPUT.waiting = null 66 | 67 | return line 68 | } 69 | 70 | 71 | function evalcx(source, sandbox) { 72 | sandbox = sandbox || {} 73 | //log('evalcx in %j: %j', Object.keys(sandbox), source) 74 | 75 | if(source == '') 76 | return sandbox 77 | 78 | // source might be "function(doc) { emit(doc._id, 1) }" 79 | source = source.replace(/;+$/, '') 80 | 81 | sandbox.XML = sandbox.XML || XML 82 | source = '(' + source + ')' 83 | 84 | try { 85 | var id = Math.floor(Math.random() * 1000*1000) 86 | var filename = '_couchdb:' + id + '.js' 87 | var script = vm.createScript(source, filename) 88 | var func = script.runInNewContext(sandbox) 89 | } catch (er) { 90 | log('Error making code: %s', er.stack) 91 | return sandbox 92 | } 93 | 94 | return func 95 | } 96 | 97 | function quit(code) { 98 | code = code || 1 99 | if(code < 0) 100 | code = -code 101 | 102 | process.exit(code) 103 | } 104 | 105 | function gc() { } 106 | 107 | 108 | function toSource() { 109 | if(typeof this == 'function') 110 | return '' + this 111 | 112 | if(this instanceof Error) 113 | return this.stack 114 | 115 | return util.inspect(this) 116 | } 117 | -------------------------------------------------------------------------------- /extra.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2011 Iris Couch 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | module.exports = main 17 | 18 | var os = require('os') 19 | var fs = require('fs') 20 | var URL = require('url') 21 | var util = require('util') 22 | var http = require('http') 23 | var async = require('async') 24 | var mkdirp = require('mkdirp') 25 | var request = require('request') 26 | var optimist = require('optimist') 27 | var pushover = require('pushover') 28 | var child_process = require('child_process') 29 | 30 | var console = require('./console') 31 | var VER = require('./package.json').version 32 | 33 | var couch = { 'log': mk_couch_log('info') 34 | , 'warn' : mk_couch_log('warn') 35 | , 'error': mk_couch_log('error') 36 | , 'debug': mk_couch_log('debug') 37 | } 38 | 39 | 40 | var opts = optimist.usage('$0') 41 | 42 | var COUCH = null 43 | var GIT_DIR = null 44 | var COUCH_PASSWORD = null 45 | var GIT_PORT = null 46 | var APPLICATION = null 47 | 48 | function main() { 49 | if(opts.argv.help) 50 | return console.log(opts.help()) 51 | 52 | console.log('Extra CouchDB daemon: %s', process.pid) 53 | couch.debug('CouchDB daemon %s: %s', VER, process.pid) 54 | 55 | var env = {} 56 | for (var k in process.env) { 57 | var match = k.match(/^_couchdb_app_(.*)$/) 58 | if(match) 59 | env[match[1]] = process.env[k] 60 | } 61 | 62 | for (k in env) 63 | couch.debug(' %s = %s', k, env[k]) 64 | 65 | if(env.port && env.password && env.couch && env.dir) 66 | return git(env) 67 | 68 | setInterval(function() { 69 | console.log('Still here') 70 | couch.log('Still in couch') 71 | }, 60000) 72 | } 73 | 74 | function git(env) { 75 | GIT_PORT = +env.port 76 | COUCH = env.couch 77 | COUCH_DIR = util.format('%s/couchjs-%s', env.dir, VER) 78 | COUCH_PASSWORD = env.password 79 | 80 | //var couch_url = util.format('http://_nodejs:%s@127.0.0.1:%d', password, couch_port) 81 | //couch.log('couch url %j', couch_url) 82 | 83 | auth('_nodejs', COUCH_PASSWORD, function(er, userCtx) { 84 | if(er) 85 | throw er 86 | 87 | var roles = userCtx.roles || [] 88 | if(userCtx.name != '_nodejs' || !~roles.indexOf('_admin')) 89 | throw new Error('Not admin: ' + JSON.stringify(res.body.userCtx)) 90 | 91 | var repos = pushover(COUCH_DIR) 92 | repos.on('push', function(push) { 93 | couch.log('Push %s/%s: %s', push.repo, push.commit, push.branch) 94 | push.accept() 95 | //couch.log('Response: %j', Object.keys(push.response)) 96 | push.response.on('finish', function() { 97 | //couch.log('Finished!') 98 | publish(push) 99 | }) 100 | }) 101 | 102 | repos.on('fetch', function(fetch) { 103 | couch.log('fetch %j', fetch.commit) 104 | fetch.accept() 105 | }) 106 | 107 | var server = http.createServer(function(req, res) { 108 | if(! req.url.match(/^\/_nodejs\/_git(\/|$)/)) 109 | return handle_http(req, res) 110 | 111 | req.pause() 112 | auth_req(req, function(er, userCtx) { 113 | if(er && er.statusCode) { 114 | res.writeHead(er.statusCode, er.headers) 115 | return res.end(er.body) 116 | } 117 | 118 | if(er) { 119 | couch.log('Bad req %s: %s', req.url, er.message) 120 | return res.end() 121 | } 122 | 123 | var roles = userCtx.roles || [] 124 | if(!~ roles.indexOf('_admin')) { 125 | couch.log('Not admin: %s, %j', req.url, userCtx) 126 | res.writeHead(401, 'Unauthorized', {'content-type':'application/json'}) 127 | return res.end('{"error":"not_authorized"}\n') 128 | } 129 | 130 | //couch.log('Handle Git: %j', req.url) 131 | repos.handle(req, res) 132 | req.resume() 133 | }) 134 | }) 135 | 136 | server.listen(GIT_PORT) 137 | }) 138 | } 139 | 140 | function handle_http(req, res) { 141 | if(! APPLICATION) { 142 | var headers = { 'content-type': 'application/json' 143 | , 'server': 'NodeJS-CouchDB/'+VER 144 | } 145 | res.writeHead(200, 'OK', headers) 146 | var body = {'ok':true} 147 | return res.end(JSON.stringify(body) + '\n') 148 | } 149 | 150 | // Clean up the vhost changes. 151 | var vhost_path = req.headers['x-couchdb-vhost-path'] 152 | if(vhost_path) { 153 | req.url = vhost_path 154 | delete req.headers['x-couchdb-vhost-path'] 155 | } 156 | 157 | APPLICATION(req, res) 158 | } 159 | 160 | function auth(user, pass, callback) { 161 | if(!COUCH) 162 | return process.nextTick(function() { callback(new Error('No _couchdb_port')) }) 163 | 164 | var url = COUCH + '/_session' 165 | if(user || pass) { 166 | url = URL.parse(url) 167 | url.auth = util.format('%s:%s', user || '', pass || '') 168 | url = URL.format(url) 169 | } 170 | 171 | //couch.log('auth: %j', url) 172 | request({'url':url, 'json':true}, function(er, res) { 173 | //couch.log('auth result: %j', res.body) 174 | if(er) 175 | return callback(er) 176 | 177 | if(res.statusCode != 200) { 178 | er = new Error('Bad status '+res.statusCode+' for auth: ' + res.body) 179 | er.statusCode = res.statusCode 180 | er.body = JSON.stringify(res.body) + '\n' 181 | er.headers = res.headers 182 | return callback(er) 183 | } 184 | 185 | return callback(null, res.body.userCtx) 186 | }) 187 | } 188 | 189 | function auth_req(req, callback) { 190 | var headers = req.headers || {} 191 | var auth_str = req.headers.authorization || '' 192 | 193 | var match = auth_str.match(/^Basic (.+)$/) 194 | if(!match) 195 | return auth(null, null, callback) 196 | 197 | try { 198 | auth_str = new Buffer(match[1], 'base64').toString() 199 | match = auth_str.match(/^([^:]+):(.+)$/) 200 | } catch (er) { 201 | return callback(er) 202 | } 203 | 204 | if(!match) 205 | return callback(new Error('Bad auth string: ' + auth_str)) 206 | 207 | auth(match[1], match[2], callback) 208 | } 209 | 210 | 211 | function publish(push) { 212 | var script = __dirname + '/checkout.sh' 213 | var repo = COUCH_DIR + '/' + push.repo 214 | 215 | var id = Math.floor(Math.random() * 1000 * 1000) 216 | var work = util.format('%s/%s/%s', COUCH_DIR, push.commit, id) 217 | 218 | mkdirp(work, function(er) { 219 | if(er) { 220 | couch.error('Failed to make working dir: %s', work) 221 | throw er 222 | } 223 | 224 | checkout() 225 | }) 226 | 227 | function checkout() { 228 | var args = [script, repo, push.commit] 229 | var opts = { 'cwd':work, 'stdio':'pipe' } 230 | 231 | var child = child_process.spawn('bash', args, opts) 232 | 233 | var output = [] 234 | 235 | child.stdout.on('data', function(x) { 236 | var msg = util.format('OUT %s', x.toString().trim()) 237 | couch.debug(msg) 238 | output.push(msg) 239 | }) 240 | child.stderr.on('data', function(x) { 241 | var msg = util.format('ERR %s', x.toString().trim()) 242 | couch.debug(msg) 243 | output.push(msg) 244 | }) 245 | 246 | child.on('exit', function(code) { 247 | if(code !== 0) { 248 | couch.error('Bad checkout: %d', code) 249 | output.forEach(function(line) { 250 | couch.error(line) 251 | }) 252 | 253 | throw new Error('Bad checkout') 254 | } 255 | 256 | couch.log('Checked out push: %s', work) 257 | fs.readFile(work+'/package.json', 'utf8', function(er, body) { 258 | if(er) 259 | throw er 260 | 261 | body = JSON.parse(body) 262 | if(!body.couchdb) 263 | return couch.warn('No "couchdb" value in pushed package.json') 264 | 265 | run_app(work, body) 266 | }) 267 | }) 268 | } 269 | } 270 | 271 | 272 | function run_app(work_dir, pkg) { 273 | var vhosts = [] 274 | , main = null 275 | 276 | if(typeof pkg.couchdb == 'string') 277 | main = pkg.couchdb 278 | else { 279 | vhosts = pkg.couchdb.vhosts || [] 280 | main = pkg.couchdb.main 281 | } 282 | 283 | couch.log('Run app %s: %j', main, vhosts) 284 | 285 | var mod_path = util.format('%s/%s', work_dir, main) 286 | try { 287 | var ok = require.resolve(mod_path) 288 | } catch (er) { 289 | return couch.error('Bad module path: %s', mod_path) 290 | } 291 | 292 | couch_mod = require(mod_path) 293 | APPLICATION = couch_mod 294 | couch.log('Installed CouchDB application') 295 | 296 | return async.forEach(vhosts, set_vhost, vhosts_set) 297 | 298 | function set_vhost(vhost, to_async) { 299 | var couch_url = URL.parse(COUCH) 300 | couch_url.auth = '_nodejs:' + COUCH_PASSWORD 301 | couch_url = URL.format(couch_url) 302 | couch.log('couch_url: %j', couch_url) 303 | 304 | var url = couch_url + '_config/vhosts/' + vhost 305 | var body = '/_nodejs' 306 | request.put({'url':url, 'json':body}, function(er, res) { 307 | if(er) 308 | return to_async(er) 309 | if(res.statusCode != 200) 310 | return to_async(new Error('Bad response '+res.statusCode+' to vhost: ' + vhost)) 311 | 312 | couch.log('Set vhost: %s', vhost) 313 | return to_async() 314 | }) 315 | } 316 | 317 | function vhosts_set(er) { 318 | if(er) 319 | throw er 320 | 321 | couch.log('Set %d vhosts for CouchDB application', vhosts.length) 322 | } 323 | } 324 | 325 | 326 | // 327 | // Utilities 328 | // 329 | 330 | function mk_couch_log(level) { 331 | if(level == 'warn') 332 | level = 'error' 333 | 334 | return logger 335 | 336 | function logger() { 337 | var str = util.format.apply(util, arguments) 338 | var msg = ['log', str, {'level':level}] 339 | msg = JSON.stringify(msg) 340 | process.stdout.write(msg + '\n') 341 | } 342 | } 343 | 344 | 345 | if(require.main === module) 346 | main() 347 | -------------------------------------------------------------------------------- /inspector.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // 3 | // couchjs replacement 4 | // 5 | // Copyright 2011 Iris Couch 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | module.exports = start 20 | 21 | if(require.main === module) 22 | main() 23 | 24 | 25 | var fs = require('fs') 26 | var util = require('util') 27 | var child_process = require('child_process') 28 | 29 | var log = require('./console').log 30 | 31 | function start(debugPort) { 32 | if(!debugPort || typeof debugPort != 'number') 33 | throw new Error('Need a listen debugPort') 34 | var webPort = debugPort + 1 35 | 36 | var cmd = __filename 37 | var args = [debugPort, webPort] 38 | var opts = 39 | { 'cwd': __dirname 40 | , 'stdio': 'pipe' 41 | , 'detached': false 42 | } 43 | 44 | log('Start inspector: %s %j %j', cmd, args, opts) 45 | var inspector = child_process.spawn(cmd, args, opts) 46 | watch_inspector(inspector) 47 | 48 | log('Enable remote debug pid=%d port=%d', process.pid, debugPort) 49 | process.debugPort = debugPort 50 | process.kill(process.pid, 'SIGUSR1') 51 | } 52 | 53 | function watch_inspector(child) { 54 | child.stderr.on('data', function(body) { 55 | log('Inspector STDERR: %s', body) 56 | }) 57 | child.stdout.on('data', function(body) { 58 | log('Inspector STDOUT: %s', body) 59 | }) 60 | 61 | child.on('exit', function(code, signal) { 62 | log('Inspector exited %d signal=%j', code, signal) 63 | process.exit(code) 64 | }) 65 | 66 | process.on('exit', function() { 67 | log('Kill inspector upon exit: %d', child.pid) 68 | process.kill(child.pid, 'SIGTERM') 69 | }) 70 | } 71 | 72 | 73 | function main() { 74 | var debugPort = +process.argv[2] 75 | var webPort = +process.argv[3] 76 | 77 | if(!debugPort || !webPort) 78 | throw new Error('Bad arguments: need debugPort and webPort') 79 | 80 | console.log('Start inspector debugPort=%j webPort=%j', debugPort, webPort) 81 | var DebugServer = require('node-inspector/lib/debug-server') 82 | var server = new DebugServer 83 | server.on('close', function() { 84 | console.log('Server closed') 85 | process.exit(0) 86 | }) 87 | 88 | server.start({'webPort':webPort, 'debugPort':debugPort}) 89 | process.on('uncaughtException', function(er) { 90 | console.log('Error:\n%s', er.stack) 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "couchjs" 2 | , "description": "Drop-in replacement for CouchDB JavaScript view server" 3 | , "keywords": [ "couchdb", "couchjs" ] 4 | , "version": "0.3.2" 5 | , "author": "Jason Smith (http://www.iriscouch.com)" 6 | , "repository": { "type":"git", "url":"https://github.com/iriscouch/couchjs" } 7 | 8 | , "engines": { "node": ">= 0.8" } 9 | , "main": "./couchjs.js" 10 | , "bin": {"couchjs-node":"./cli.js", "couchjs-extra":"./extra.js"} 11 | 12 | , "couchdb": { "main":"./couchdb.js" 13 | , "vhosts": ["127.0.0.1.xip.io"] 14 | } 15 | 16 | , "bundledDependencies": ["node-inspector"] 17 | 18 | , "dependencies": { "optimist": "~0.3.4" 19 | , "async" : "~0.2.5" 20 | , "mkdirp" : "~0.3.4" 21 | , "fibers" : "~1.0.0" 22 | , "request" : "~2.9.203" 23 | , "pushover": "~1.2.1" 24 | , "defaultable": "~0.7.2" 25 | , "node-inspector": "git://github.com/iriscouch/node-inspector#couchjs" 26 | } 27 | 28 | , "devDependencies": { "tap": "~0.2.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /stream.js: -------------------------------------------------------------------------------- 1 | // Text line stream 2 | // 3 | // Copyright 2011 Iris Couch 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | module.exports = LineStream 18 | module.exports.v2 = LineStream2 19 | 20 | var stream = require('stream') 21 | var util = require('util') 22 | 23 | 24 | util.inherits(LineStream2, stream.Transform) 25 | function LineStream2 () { 26 | if(! (this instanceof LineStream2)) 27 | return new LineStream2 28 | 29 | stream.Transform.call(this) 30 | this.setEncoding('utf8') 31 | } 32 | 33 | LineStream2.prototype._transform = function(message, encoding, done) { 34 | var self = this 35 | 36 | message = message.toString(encoding) 37 | var lines = message.split(/\n/) 38 | 39 | // If the data ends in "\n" this will be ""; otherwise the final partial line. 40 | var remainder = lines.pop() 41 | if(remainder) 42 | this.unshift(remainder) 43 | 44 | lines.forEach(function(line) { 45 | self.push(line) 46 | }) 47 | 48 | done() 49 | } 50 | 51 | util.inherits(LineStream, stream) 52 | function LineStream () { 53 | var self = this 54 | stream.call(self) 55 | 56 | self.readable = true 57 | self.writable = true 58 | 59 | self.buffer = '' 60 | self.downstream = null 61 | 62 | self.on('pipe', function(upstream) { 63 | upstream.on('end', function(data, encoding) { 64 | self.emit('end', data, encoding) 65 | }) 66 | }) 67 | } 68 | 69 | 70 | LineStream.prototype.write = function(data, encoding) { 71 | var self = this 72 | 73 | data = data || '' 74 | if(typeof data != 'string') 75 | return self.error(new Error('Data was not a string: ' + util.inspect(data))) 76 | 77 | self.buffer += data 78 | var lines = self.buffer.split(/\n/) 79 | self.buffer = lines.pop() // If the data ended in "\n" this will be ""; otherwise the final partial line. 80 | 81 | lines.forEach(function(line) { 82 | self.emit('data', line) 83 | }) 84 | } 85 | 86 | 87 | LineStream.prototype.end = function(data, encoding) { 88 | var self = this 89 | 90 | self.is_ending = true 91 | self.writable = false 92 | 93 | // Always call write, even with no data, so it can fire the "end" event. 94 | self.write(data) 95 | } 96 | 97 | 98 | LineStream.prototype.error = function(er) { 99 | var self = this 100 | 101 | self.readable = false 102 | self.writable = false 103 | self.emit('error', er) 104 | 105 | // The write() method sometimes returns this value, so if there was an error, make write() return false. 106 | return false 107 | } 108 | -------------------------------------------------------------------------------- /test/experiment.js: -------------------------------------------------------------------------------- 1 | var vm = require('vm') 2 | var util = require('util') 3 | 4 | var STATE = 'wait' 5 | , v = 'vm' 6 | 7 | function main() { 8 | process.debugPort = 5859 9 | process.kill(process.pid, 'SIGUSR1') 10 | 11 | setTimeout(function() { stuff(0) }, 1000) 12 | } 13 | 14 | function stuff(count) { 15 | console.log('Doing stuff: %d', count) 16 | //debugger 17 | STATE = 'vm' 18 | console.log('More stuff: %d', count) 19 | if(STATE == 'done') 20 | console.log('Done') 21 | else if(STATE == 'code') 22 | setTimeout(code, 1000) 23 | else if(STATE == 'eval') 24 | test_eval() 25 | else if(STATE == 'vm') 26 | test_vm() 27 | else if(STATE == 'wait') 28 | setTimeout(function() { stuff(count+1) }, 1000) 29 | else 30 | throw new Error('Unknown state: ' + STATE) 31 | } 32 | 33 | function code() { 34 | var code = 35 | [ 'var foo = "in the code"' 36 | , 'console.log("This is some code")' 37 | , 'debugger' 38 | , 'console.log("foo = " + foo)' 39 | ].join('\n') 40 | 41 | var runner = Function([], code) 42 | console.log('Run runner in 1s') 43 | setTimeout(run_runner, 1000) 44 | 45 | function run_runner() { 46 | console.log('About to run runner') 47 | debugger 48 | runner() 49 | console.log('Runner done') 50 | } 51 | } 52 | 53 | function test_eval() { 54 | console.log('Test eval in 1s') 55 | setTimeout(run_eval, 1000) 56 | 57 | var code = 58 | [ 'var foo = "in eval"' 59 | , 'console.log("This is eval")' 60 | , 'debugger' 61 | , 'console.log("foo = " + foo)' 62 | ].join('\n') 63 | 64 | function run_eval() { 65 | console.log('Run eval now') 66 | debugger 67 | eval(code) 68 | } 69 | } 70 | 71 | function test_vm() { 72 | console.log('Test vm') 73 | 74 | var code = 75 | [ 'var i = 10' 76 | , 'setTimeout(hello, 1000)' 77 | , '' 78 | , 'function hello() {' 79 | , ' debugger' 80 | , ' console.log("Hello: " + i)' 81 | , ' if(--i)' 82 | , ' setTimeout(hello, 1000)' 83 | , '}' 84 | ].join('\n') 85 | 86 | console.log('Run vm now') 87 | var filename = '_couchdb:code.js' 88 | 89 | var sandbox = {} 90 | , ok = ['console', 'setTimeout'] 91 | 92 | ok.forEach(function(key) { 93 | sandbox[key] = global[key] 94 | }) 95 | 96 | var ctx = vm.createContext(sandbox) 97 | var script = vm.createScript(code, filename) 98 | 99 | var r = script.runInNewContext(sandbox) 100 | console.log('Result:\n%s', util.inspect(r, false, 10)) 101 | return r 102 | } 103 | 104 | if(require.main === module) 105 | main() 106 | -------------------------------------------------------------------------------- /xml.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Iris Couch 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = XML 16 | 17 | function XML () { 18 | this.foo = 'bar' 19 | } 20 | 21 | XML.prototype.toXMLString = function() { 22 | return '\n test\n' 23 | } 24 | --------------------------------------------------------------------------------