├── README.md ├── patch-promise.js └── jsgi-node.js /README.md: -------------------------------------------------------------------------------- 1 | Node-CommonJS is a project that provides compatibility adapters for implementing 2 | CommonJS APIs on top of Node, where Node doesn't already implement CommonJS. 3 | -------------------------------------------------------------------------------- /patch-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A minimal monkey patch for Node to achieve CommonJS promise compliance, by 3 | * adding a conformant "then" function. 4 | * 5 | * The "then" function supports chaining (it returns a promise that is given the result 6 | * of the callback computation), correct functional data flow (computations affect 7 | * output, and not input), and handling of promises as the return value of callbacks 8 | * (and properly deferring the fulfillment of the returned promise). 9 | * 10 | * The first argument to "then" is the function to be executed when the promise is 11 | * successfully fulfilled. 12 | * The second argument to "then" is the function to be executed when the promise 13 | * is fulfilled in an error state. 14 | * 15 | * If the either of the callbacks returns a normal value, the promise returned by "then" 16 | * is fulfilled with that value. If they throw an exception, the promise returned by 17 | * "then"is put in an error state. If they return a promise, the promise returned by 18 | * "then" will be fulfilled when the returned promise is fulfilled (with the state/value) 19 | * of that promise. 20 | * 21 | * For example: 22 | 23 | function printFirstAndLast(itemsDeferred){ 24 | findFirst(itemsDeferred).then(sys.puts); 25 | findLast(itemsDeferred).then(sys.puts); 26 | } 27 | function findFirst(itemsDeferred){ 28 | return itemsDeferred.then(function(items){ 29 | return items[0]; 30 | }); 31 | } 32 | function findLast(itemsDeferred){ 33 | return itemsDeferred.then(function(items){ 34 | return items[items.length - 1]; 35 | }); 36 | } 37 | 38 | var promise = new process.Promise(); 39 | printFirstAndLast(promise); 40 | // nothing printed yet, and then: 41 | promise.emitSuccess([1,2,3,4,5]); 42 | //prints: 43 | 1 44 | 5 45 | */ 46 | 47 | process.Promise.prototype.then = function (ok, error) { 48 | var returnedPromise = new process.Promise(); 49 | 50 | if (ok) { 51 | this.addCallback(createPropagator(ok)); 52 | } 53 | else { 54 | this.addCallback(function (value) { 55 | returnedPromise.emitSuccess(value); 56 | }); 57 | } 58 | if (error) { 59 | this.addErrback(createPropagator(error)); 60 | } 61 | else { 62 | this.addErrback(function (error) { 63 | returnedPromise.emitError(error); 64 | }); 65 | } 66 | 67 | return returnedPromise; 68 | 69 | function createPropagator (callback) { 70 | return function (value) { 71 | try { 72 | value = callback(value); 73 | if (value && (typeof value.then === "function")) { 74 | // return value is a promise, wait until is fulfilled to fulfill the returned promise 75 | value.then(function (value) { 76 | returnedPromise.emitSuccess(value); 77 | }, 78 | function (error) { 79 | returnedPromise.emitError(error); 80 | }); 81 | } 82 | else { 83 | returnedPromise.emitSuccess(value); 84 | } 85 | } 86 | catch (error) { 87 | returnedPromise.emitError(error); 88 | } 89 | }; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /jsgi-node.js: -------------------------------------------------------------------------------- 1 | /* 2 | JSGI 0.3 Adapter for Node 3 | 4 | To use provide a JSGI application (can be application stack) to the start 5 | function: 6 | 7 | require("jsgi-node").start(function(request){ 8 | var requestBody = request.input.read().decodeToString("UTF-8"); 9 | return { 10 | status:200, 11 | headers:{}, 12 | body:["echo: " + requestBody] 13 | }; 14 | }); 15 | 16 | This adapter should conform to the JSGI 0.3 (with promises) for full 17 | asynchronous support. For example: 18 | 19 | var posix = require("posix"); 20 | require("jsgi-node").start(function(request){ 21 | var promise = new process.Promise(); 22 | posix.cat("jsgi-node.js").addCallback(function(body){ 23 | promise.emitSuccess({ 24 | status: 200, 25 | headers: {}, 26 | body: [body] 27 | }); 28 | }); 29 | return promise; 30 | }); 31 | */ 32 | 33 | var 34 | sys = require( "sys" ), 35 | url = require( "url" ); 36 | 37 | function Request( request ) { 38 | var 39 | uri = url.parse( request.url ), 40 | headers = request.headers, 41 | namePort = headers.host.split( ":" ), 42 | lowerCaseHeaders = {}; 43 | 44 | this.method = request.method; 45 | this.scriptName = ""; 46 | this.pathInfo = uri.pathname; 47 | this.queryString = uri.query || ""; 48 | this.serverName = namePort[ 0 ]; 49 | this.serverPort = namePort[ 1 ] || 80; 50 | this.scheme = "http"; 51 | this.env = { node: { request: request } }; 52 | this.input = new Input( request ); 53 | 54 | for(var i in headers){ 55 | lowerCaseHeaders[i.toLowerCase()] = headers[i]; 56 | } 57 | this.headers = lowerCaseHeaders; 58 | this.version = [ request.httpVersionMajor, request.httpVersionMinor ]; 59 | } 60 | 61 | Request.prototype.jsgi = { 62 | version: [ 0, 3 ], 63 | multithread: false, 64 | multiprocess: true, 65 | async: true, 66 | runOnce: false, 67 | errors: { 68 | print: sys.puts, 69 | flush: function(){} 70 | } 71 | }; 72 | 73 | function Input( request ) { 74 | var 75 | inputBuffer = [], 76 | inputLength = 0, 77 | inputPromise, 78 | waitingForLength = Infinity, 79 | requestCompletePromise = new process.Promise(); 80 | 81 | request 82 | .addListener( "body", function( data ) { 83 | inputBuffer.push( data ); 84 | inputLength += data.length; 85 | if ( inputLength >= waitingForLength ) { 86 | inputPromise.emitSuccess(); 87 | } 88 | }) 89 | .addListener( "complete", function() { 90 | requestCompletePromise.emitSuccess(); 91 | }); 92 | 93 | this.read = function( length ) { 94 | return { 95 | decodeToString: function( encoding ) { 96 | request.setBodyEncoding( encoding ); 97 | if ( !length ) { 98 | if ( !requestCompletePromise.hasFired ) { 99 | requestCompletePromise.wait(); 100 | } 101 | } 102 | 103 | else if ( length > inputLength ) { 104 | waitingForLength = length; 105 | inputPromise = new process.Promise(); 106 | inputPromise.wait(); 107 | waitingForLength = Infinity; 108 | } 109 | 110 | var chunk = inputBuffer.join(""); 111 | var keepInBuffer = length ? chunk.substring( length ) : ""; 112 | 113 | inputBuffer = [ keepInBuffer ]; 114 | inputLength = keepInBuffer.length; 115 | 116 | return length ? chunk.substring( 0, length ) : chunk; 117 | } 118 | }; 119 | }; 120 | } 121 | 122 | function Response( response ) { 123 | var started = false; 124 | return handle; 125 | 126 | function handle( data, notDone ) { 127 | var forEachResult; 128 | 129 | if ( typeof data.then === "function" ) { 130 | data.then( 131 | handle, 132 | function( error ) { 133 | handle({ status:500, headers:{}, body:[error.message] }); 134 | }, 135 | function( data ){ 136 | handle( data, true); 137 | } 138 | ); 139 | 140 | return; 141 | } 142 | 143 | if ( !started ) { 144 | started = true; 145 | response.sendHeader( data.status, data.headers ); 146 | } 147 | 148 | try { 149 | if ( typeof data.body.forEach !== "function" ) { 150 | throw new Error("The body does not have a forEach function"); 151 | } 152 | 153 | forEachResult = data.body.forEach( function( chunk, encoding ) { 154 | response.sendBody( chunk, encoding ); 155 | }); 156 | 157 | if ( !notDone && forEachResult && ( typeof forEachResult.then === "function" ) ) { 158 | forEachResult.then( function() { 159 | response.finish(); 160 | }); 161 | } 162 | 163 | else if ( !notDone ) { 164 | response.finish(); 165 | } 166 | } 167 | 168 | catch( e ) { 169 | response.sendBody( "Error: " + e.stack ); 170 | response.finish(); 171 | } 172 | } 173 | } 174 | 175 | function Listener( app ) { 176 | return function( request, response ) { 177 | request = new Request( request ); 178 | var respond = new Response( response ); 179 | 180 | var jsgiResponse; 181 | 182 | setTimeout( function() { 183 | // need to do this as the next event so that the request can be in a state to feed the input properly 184 | try { 185 | jsgiResponse = app( request ) 186 | } catch( error ) { 187 | jsgiResponse = { status:500, headers:{}, body:[error.stack] }; 188 | } 189 | 190 | respond( jsgiResponse ); 191 | }, 0 ); 192 | } 193 | } 194 | 195 | exports.start = function( app, options ) { 196 | app = new Listener( app ); 197 | options = options || {}; 198 | 199 | var port = options.port || 8080; 200 | 201 | require( "http" ).createServer( app ).listen( port ); 202 | sys.puts( "Server running on port " + port ); 203 | }; 204 | 205 | // Patch the Promise constructor if it is not correct, this is a very minimal 206 | // fix for promises in Node 207 | if ( typeof process.Promise.prototype.then !== "function" ) { 208 | process.Promise.prototype.then = function( ok, error ) { 209 | this.addCallback( ok ); 210 | this.addErrback( error ); 211 | return this; 212 | }; 213 | } --------------------------------------------------------------------------------