Created
October 13, 2010 14:48
-
-
Save cmilfont/624173 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Module dependencies. | |
*/ | |
var express = require('express'), | |
connect = require('connect'); | |
var app = module.exports = express.createServer(); | |
var Mu = require('./vendor/mu'); | |
//http://comments.gmane.org/gmane.comp.lang.javascript.nodejs/2378 | |
// randomString returns a pseude-random ASCII string which contains at least the specified number of bits of entropy | |
// the return value is a string of length ⌈bits/6⌉ of characters from the base64 alphabet | |
function randomString(bits){var chars,rand,i,ret | |
chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' | |
ret='' | |
// in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53) | |
while(bits > 0){ | |
rand=Math.floor(Math.random()*0x100000000) // 32-bit integer | |
// base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters. | |
for(i=26; i>0 && bits>0; i-=6, bits-=6) ret+=chars[0x3F & rand >>> i]} | |
return ret | |
} | |
var uuid = function() { | |
return randomString(64); | |
} | |
// var tmpl = "Hello {{> part}}. Your name is: {{name}}!"; | |
// var partials = {part: "World"}; | |
// var compiled = Mu.compileText(tmpl, partials); | |
// | |
// compiled({name: "Chris"}).addListener('data', function (c) { sys.puts(c) }); | |
// Configuration | |
app.configure(function(){ | |
app.set('views', __dirname + '/views'); | |
app.use(connect.bodyDecoder()); | |
app.use(connect.methodOverride()); | |
app.use(connect.compiler({ src: __dirname + '/public', enable: ['less'] })); | |
app.use(app.router); | |
app.use(connect.staticProvider(__dirname + '/public')); | |
}); | |
app.configure('development', function(){ | |
app.use(connect.errorHandler({ dumpExceptions: true, showStack: true })); | |
}); | |
app.configure('production', function(){ | |
app.use(connect.errorHandler()); | |
}); | |
// Routes | |
app.get('/', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('{"couchdb":"Welcome","version":"1.0.1"}'); | |
}); | |
app.get('/_config', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send(JSON.stringify({ | |
httpd_design_handlers: { _info: '{couch_httpd_db, handle_design_info_req}' | |
, _list: '{couch_httpd_show, handle_view_list_req}' | |
, _rewrite: '{couch_httpd_rewrite, handle_rewrite_req}' | |
, _show: '{couch_httpd_show, handle_doc_show_req}' | |
, _update: '{couch_httpd_show, handle_doc_update_req}' | |
, _view: '{couch_httpd_view, handle_view_req}' | |
} | |
, uuids: { algorithm: 'sequential' } | |
, stats: { rate: '1000', samples: '[0, 60, 300, 900]' } | |
, httpd_global_handlers: | |
{ '/': '{couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>}' | |
, _active_tasks: '{couch_httpd_misc_handlers, handle_task_status_req}' | |
, _all_dbs: '{couch_httpd_misc_handlers, handle_all_dbs_req}' | |
, _config: '{couch_httpd_misc_handlers, handle_config_req}' | |
, _log: '{couch_httpd_misc_handlers, handle_log_req}' | |
, _oauth: '{couch_httpd_oauth, handle_oauth_req}' | |
, _replicate: '{couch_httpd_misc_handlers, handle_replicate_req}' | |
, _restart: '{couch_httpd_misc_handlers, handle_restart_req}' | |
, _session: '{couch_httpd_auth, handle_session_req}' | |
, _stats: '{couch_httpd_stats_handlers, handle_stats_req}' | |
, _utils: '{couch_httpd_misc_handlers, handle_utils_dir_req, "/usr/local/Cellar/couchdb/1.0.1/share/couchdb/www"}' | |
, _uuids: '{couch_httpd_misc_handlers, handle_uuids_req}' | |
, 'favicon.ico': '{couch_httpd_misc_handlers, handle_favicon_req, "/usr/local/Cellar/couchdb/1.0.1/share/couchdb/www"}' | |
} | |
, attachments: | |
{ compressible_types: 'text/*, application/javascript, application/json, application/xml' | |
, compression_level: '8' | |
} | |
, query_server_config: { reduce_limit: 'true' } | |
, replicator: | |
{ max_http_pipeline_size: '10' | |
, max_http_sessions: '10' | |
} | |
, log: | |
{ file: '/usr/local/var/log/couchdb/couch.log' | |
, include_sasl: 'true' | |
, level: 'info' | |
} | |
, query_servers: { javascript: '/usr/local/Cellar/couchdb/1.0.1/bin/couchjs /usr/local/Cellar/couchdb/1.0.1/share/couchdb/server/main.js' } | |
, daemons: | |
{ auth_cache: '{couch_auth_cache, start_link, []}' | |
, db_update_notifier: '{couch_db_update_notifier_sup, start_link, []}' | |
, external_manager: '{couch_external_manager, start_link, []}' | |
, httpd: '{couch_httpd, start_link, []}' | |
, query_servers: '{couch_query_servers, start_link, []}' | |
, stats_aggregator: '{couch_stats_aggregator, start, []}' | |
, stats_collector: '{couch_stats_collector, start, []}' | |
, uuids: '{couch_uuids, start, []}' | |
, view_manager: '{couch_view, start_link, []}' | |
} | |
, httpd: | |
{ allow_jsonp: 'false' | |
, authentication_handlers: '{couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}' | |
, bind_address: '127.0.0.1' | |
, default_handler: '{couch_httpd_db, handle_request}' | |
, max_connections: '2048' | |
, port: '5984' | |
, secure_rewrites: 'true' | |
, vhost_global_handlers: '_utils, _uuids, _session, _oauth, _users' | |
} | |
, httpd_db_handlers: | |
{ _changes: '{couch_httpd_db, handle_changes_req}' | |
, _compact: '{couch_httpd_db, handle_compact_req}' | |
, _design: '{couch_httpd_db, handle_design_req}' | |
, _temp_view: '{couch_httpd_view, handle_temp_view_req}' | |
, _view_cleanup: '{couch_httpd_db, handle_view_cleanup_req}' | |
} | |
, couch_httpd_auth: | |
{ auth_cache_size: '50' | |
, authentication_db: '_users' | |
, authentication_redirect: '/_utils/session.html' | |
, require_valid_user: 'false' | |
, timeout: '600' | |
} | |
, couchdb: | |
{ database_dir: '/usr/local/var/lib/couchdb' | |
, delayed_commits: 'true' | |
, max_attachment_chunk_size: '4294967296' | |
, max_dbs_open: '100' | |
, max_document_size: '4294967296' | |
, os_process_timeout: '5000' | |
, uri_file: '/usr/local/var/lib/couchdb/couch.uri' | |
, util_driver_dir: '/usr/local/Cellar/couchdb/1.0.1/lib/couchdb/erlang/lib/couch-1.0.1/priv/lib' | |
, view_index_dir: '/usr/local/var/lib/couchdb' | |
} | |
})); | |
}); | |
app.get('/_config/query_servers', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('{"javascript":"/usr/local/Cellar/couchdb/1.0.1/bin/couchjs /usr/local/Cellar/couchdb/1.0.1/share/couchdb/server/main.js"}'); | |
}); | |
app.get('/_config/native_query_servers', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('{}'); | |
}); | |
app.get('/_stats', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('{"couchdb":{"open_databases":{"description":"number of open databases","current":5.0,"sum":5.0,"mean":0.0,"stddev":0.021,"min":-2,"max":4},"auth_cache_hits":{"description":"number of authentication cache hits","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"auth_cache_misses":{"description":"number of authentication cache misses","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"database_reads":{"description":"number of times a document was read from a database","current":243.0,"sum":243.0,"mean":0.004,"stddev":0.345,"min":0,"max":66},"database_writes":{"description":"number of times a database was changed","current":195.0,"sum":195.0,"mean":0.003,"stddev":0.219,"min":0,"max":27},"request_time":{"description":"length of a request inside CouchDB without MochiWeb","current":6606.348,"sum":6606.348,"mean":157.294,"stddev":351.139,"min":1.0,"max":1858.0},"open_os_files":{"description":"number of file descriptors CouchDB has open","current":7.0,"sum":7.0,"mean":0.0,"stddev":0.04,"min":-3,"max":5}},"httpd":{"requests":{"description":"number of HTTP requests","current":565.0,"sum":565.0,"mean":0.008,"stddev":0.511,"min":0,"max":62},"bulk_requests":{"description":"number of bulk requests","current":10.0,"sum":10.0,"mean":0.0,"stddev":0.015,"min":0,"max":3},"view_reads":{"description":"number of view reads","current":54.0,"sum":54.0,"mean":0.001,"stddev":0.135,"min":0,"max":33},"clients_requesting_changes":{"description":"number of clients for continuous _changes","current":0,"sum":0,"mean":0.0,"stddev":0.027,"min":-5,"max":5},"temporary_view_reads":{"description":"number of temporary view reads","current":114.0,"sum":114.0,"mean":0.002,"stddev":0.216,"min":0,"max":33}},"httpd_request_methods":{"DELETE":{"description":"number of HTTP DELETE requests","current":28.0,"sum":28.0,"mean":0.0,"stddev":0.029,"min":0,"max":5},"HEAD":{"description":"number of HTTP HEAD requests","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"POST":{"description":"number of HTTP POST requests","current":183.0,"sum":183.0,"mean":0.003,"stddev":0.241,"min":0,"max":34},"PUT":{"description":"number of HTTP PUT requests","current":175.0,"sum":175.0,"mean":0.003,"stddev":0.206,"min":0,"max":27},"MOVE":{"description":"number of HTTP MOVE requests","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"GET":{"description":"number of HTTP GET requests","current":179.0,"sum":179.0,"mean":0.003,"stddev":0.22,"min":0,"max":36},"COPY":{"description":"number of HTTP COPY requests","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null}},"httpd_status_codes":{"400":{"description":"number of HTTP 400 Bad Request responses","current":29.0,"sum":29.0,"mean":0.0,"stddev":0.041,"min":0,"max":5},"201":{"description":"number of HTTP 201 Created responses","current":194.0,"sum":194.0,"mean":0.003,"stddev":0.214,"min":0,"max":27},"403":{"description":"number of HTTP 403 Forbidden responses","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"409":{"description":"number of HTTP 409 Conflict responses","current":2.0,"sum":2.0,"mean":0.0,"stddev":0.008,"min":0,"max":2},"200":{"description":"number of HTTP 200 OK responses","current":301.0,"sum":301.0,"mean":0.004,"stddev":0.318,"min":0,"max":35},"202":{"description":"number of HTTP 202 Accepted responses","current":4.0,"sum":4.0,"mean":0.0,"stddev":0.008,"min":0,"max":1},"404":{"description":"number of HTTP 404 Not Found responses","current":3.0,"sum":3.0,"mean":0.0,"stddev":0.009,"min":0,"max":2},"301":{"description":"number of HTTP 301 Moved Permanently responses","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"405":{"description":"number of HTTP 405 Method Not Allowed responses","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"500":{"description":"number of HTTP 500 Internal Server Error responses","current":12.0,"sum":12.0,"mean":0.0,"stddev":0.032,"min":0,"max":8},"401":{"description":"number of HTTP 401 Unauthorized responses","current":null,"sum":null,"mean":null,"stddev":null,"min":null,"max":null},"304":{"description":"number of HTTP 304 Not Modified responses","current":16.0,"sum":16.0,"mean":0.0,"stddev":0.033,"min":0,"max":7},"412":{"description":"number of HTTP 412 Precondition Failed responses","current":1.0,"sum":1.0,"mean":0.0,"stddev":0.004,"min":0,"max":1}}}'); | |
}); | |
app.get('/_active_tasks', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('[]'); | |
}); | |
app.get('/_session', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send('{"ok":true,"userCtx":{"name":null,"roles":["_admin"]},"info":{"authentication_db":"_users","authentication_handlers":["oauth","cookie","default"],"authenticated":"default"}}'); | |
}); | |
app.get('/_uuids', function(req, res){ | |
//?count= | |
var resp = {"uuids" : []}; | |
for (var i = req.param("count") - 1; i >= 0; i--){ | |
resp.uuids.push(uuid()); | |
}; | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
res.send(JSON.stringify(resp)); | |
}); | |
var dbs = {}; | |
var dbsIndex = []; | |
var addDb = function(name) { | |
// console.log("----"); | |
// console.log(name); | |
// console.log("----"); | |
dbs[name] = {docs: [] }; | |
dbs[name].indexed = dbsIndex.push(name) - 1; | |
} | |
//for test | |
// addDb("user"); | |
app.get('/_all_dbs', function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var resp = []; | |
for (var i = dbsIndex.length - 1; i >= 0; i--){ | |
if (dbsIndex[i]) { | |
resp.push(dbsIndex[i]); | |
} | |
}; | |
res.send(JSON.stringify(resp)); | |
}); | |
app.put("/:database", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var name = req.param("database"); | |
if (!dbs[name]) { | |
addDb(name); | |
res.send(JSON.stringify({"ok" : true}), 201); | |
} else { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 412); | |
} | |
}); | |
app.del("/:database", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var name = req.param("database"); | |
if (!dbs[name]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
delete dbsIndex[dbs[name].indexed]; | |
// dbs[name] = null; | |
delete dbs[name]; | |
res.send(JSON.stringify({"ok" : true})); | |
} | |
}); | |
app.get("/:database", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
res.send('{"db_name":"'+db+'","doc_count":0,"doc_del_count":3,"update_seq":10,"purge_seq":0,"compact_running":false,"disk_size":8281,"instance_start_time":"1285991334297858","disk_format_version":5,"committed_update_seq":10}'); | |
} | |
}); | |
app.get("/:database/_changes", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
res.send('{"results":[ {"seq":5,"id":"5735f9a0d32a4fb493b6fe8eef0063cf","changes":[{"rev":"3-c445f7f90c831107c3fcaef8248556df"}],"deleted":true}, {"seq":9,"id":"5e689317ae1d8aa9cf255365a867c8e7","changes":[{"rev":"3-c445f7f90c831107c3fcaef8248556df"}],"deleted":true}, {"seq":10,"id":"renato.elias","changes":[{"rev":"4-a5379906b0cc22bb4a0d1c66620b4801"}],"deleted":true}],"last_seq":10}'); | |
} | |
}); | |
app.post("/:database/_compact", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
res.send(JSON.stringify({"ok" : true})); | |
} | |
}); | |
app.get("/:database/_all_docs", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
var rows = []; | |
var db = dbs[db].docs; | |
for(var k in db) { | |
if (db[k] != null) { | |
rows.push({"id" : db[k]._id , "key" : db[k]._id , "value" : {"rev" : db[k]._rev }}); | |
} | |
} | |
res.send('{"total_rows": '+rows.length+' ,"offset":0,"rows":'+JSON.stringify(rows)+'}'); | |
} | |
}); | |
var addDoc = function(doc, db) { | |
if (!doc._id) { | |
doc._id = uuid(); | |
} | |
doc._rev = "1-"+uuid(); | |
return dbs[db].docs.push(doc); | |
} | |
var insertDoc = function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
// console.log("----"); | |
// console.log(dbs); | |
// console.log(db); | |
// console.log("----"); | |
// console.log("----"); | |
// console.log(req.url); | |
// console.log(req.originalUrl); | |
// console.log(req.params); | |
// console.log(req.body); | |
// console.log("----"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
if (req.param("id")) { | |
req.body._id = req.param("id"); | |
} | |
var doc = addDoc(req.body , db); | |
res.send(JSON.stringify({"ok" : true , id : doc._id })); | |
} | |
}; | |
app.put("/:database/:id", insertDoc) ; | |
app.post("/:database", insertDoc) ; | |
app.post("/:database/_compact/:id", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var params = req.params; | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
var id = "_design/"+req.param("id"); | |
if (!dbs[db].docs[id]) { | |
res.send('{"error":"not_found","reason":"missing"}', 404); | |
} else { | |
res.send(JSON.stringify({"ok" : true })); | |
} | |
} | |
}); | |
app.post("/:database/_bulk_docs", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
var process = []; | |
var docs = req.body.docs; | |
for (var i=0; i < docs.length; i++) { | |
var row = addDoc(docs[i] , db); | |
process.push({"ok" : true , id : row._id }); | |
}; | |
res.send(JSON.stringify(process)); | |
} | |
}); | |
var map = function(calling, name) { | |
return dbs[name].docs.map(function(doc) { | |
var emit; | |
this.emit = function() { | |
emit = arguments; | |
} | |
console.log(calling.toString()); | |
calling.call(this, doc); | |
return emit; | |
}).filter(function(element, index, array) { | |
//remove undefined elements | |
return element | |
}); | |
} | |
var reduce = function(key, calling) { | |
return key.reduce(function(doc) { | |
var sum; | |
this.sum = function() { | |
return false; | |
} | |
return calling.call(this, doc._id, doc); | |
}); | |
} | |
app.post("/:database/_temp_view", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
console.log(req.param()); | |
// var rows = map(req.param("map"), db); | |
rows = []; | |
// var db = dbs[db].docs; | |
// for(var k in db) { | |
// if (db[k] !== null && db[k]._id.substr(0,7) !== '_design') { | |
// rows.push({"id" : db[k]._id , "key" : db[k]._id , "value" : {"rev" : db[k]._rev }}); | |
// | |
// } | |
// } | |
var resp = '{"total_rows": '+rows.length+' ,"offset":0,"rows":'+JSON.stringify(rows)+'}'; | |
res.send(resp); | |
} | |
}); | |
app.get("/:database/_design/:id/_info", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var params = req.params; | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
var id = "_design/"+req.param("id"); | |
if (!dbs[db].docs[id]) { | |
res.send('{"error":"not_found","reason":"missing"}', 404); | |
} else { | |
res.send('{"name": "'+req.param("id")+'","view_index":{"signature":"73fe79b20ff06e341257fa78231ca87e","language":"javascript","disk_size":51,"updater_running":true,"compact_running":false,"waiting_commit":false,"waiting_clients":0,"update_seq":0,"purge_seq":0}}'); | |
} | |
} | |
}); | |
app.get("/:database/_design/:id/_view/:view", function(req, res , next){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var params = req.params; | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
next(); | |
} else { | |
var id = "_design/"+req.param("id"); | |
if (!dbs[db].docs[id]) { | |
next(); | |
} else { | |
var rows = []; | |
var db = dbs[db].docs; | |
for(var k in db) { | |
if (db[k] !== null && db[k]._id.substr(0,7) !== '_design') { | |
rows.push({"id" : db[k]._id , "key" : db[k]._id , "value" : {"rev" : db[k]._rev }}); | |
} | |
} | |
res.send('{"total_rows": '+rows.length+' ,"offset":0,"rows":'+JSON.stringify(rows)+'}'); | |
} | |
} | |
}); | |
app.del("/:database/:id", function(req, res){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
res.send('{"error":"not_found","reason":"no_db_file"}', 404); | |
} else { | |
if (!dbs[db].docs[req.param("id")]) { | |
res.send('{"error":"not_found","reason":"missing"}', 404); | |
} else { | |
// dbs[db].docs[req.param("id")] = null; | |
delete dbs[db].docs[req.param("id")]; | |
res.send(JSON.stringify({"ok" : true})); | |
} | |
} | |
// | |
}); | |
app.get("/:database/*", function(req, res , next){ | |
res.header("Content-Type", 'text/plain;charset=utf-8'); | |
var params = req.params; | |
var db = req.param("database"); | |
if (!dbs[db]) { | |
// res.send('{"error":"not_found","reason":"10no_db_file"}', 404); | |
next(); | |
} else { | |
var id = req.params[0]; | |
if (!dbs[db].docs[id]) { | |
next(); | |
//res.send('{"error":"not_found","reason":"missing"}', 404); | |
} else { | |
res.send(JSON.stringify(dbs[db].docs[id])); | |
} | |
} | |
}); | |
// res.send(404); | |
//any error: {"error":"illegal_database_name","reason":"Only lowercase characters (a-z), digits (0-9), and any of the characters _, $, (, ), +, -, and / are allowed. Must begin with a letter."} | |
// Only listen on $ node app.js | |
if (!module.parent) app.listen(3000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment