THRIFT-1033: node.js target and lib

git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1056613 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/src/generate/t_js_generator.cc b/compiler/cpp/src/generate/t_js_generator.cc
index bb1033c..4aa6811 100644
--- a/compiler/cpp/src/generate/t_js_generator.cc
+++ b/compiler/cpp/src/generate/t_js_generator.cc
@@ -41,9 +41,18 @@
                 const std::map<std::string, std::string>& parsed_options,
                 const std::string& option_string) :
      t_oop_generator(program) {
-     (void) parsed_options;
      (void) option_string;
-     out_dir_base_ = "gen-js";
+     
+     std::map<std::string, std::string>::const_iterator iter;
+     
+     iter = parsed_options.find("node");
+     gen_node_ = (iter != parsed_options.end());
+
+     if (gen_node_) {
+       out_dir_base_ = "gen-nodejs";
+     } else {
+       out_dir_base_ = "gen-js";
+     }
   }
 
   /**
@@ -64,6 +73,8 @@
   void generate_xception (t_struct*   txception);
   void generate_service  (t_service*  tservice);
 
+  std::string render_recv_throw(std::string var);
+  std::string render_recv_return(std::string var);
 
   std::string render_const_value(t_type* type, t_const_value* value);
 
@@ -72,7 +83,7 @@
    * Structs!
    */
   void generate_js_struct(t_struct* tstruct, bool is_exception);
-  void generate_js_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false);
+  void generate_js_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false, bool is_exported=true);
   void generate_js_struct_reader(std::ofstream& out, t_struct* tstruct);
   void generate_js_struct_writer(std::ofstream& out, t_struct* tstruct);
   void generate_js_function_helpers(t_function* tfunction);
@@ -147,7 +158,7 @@
 
   std::string js_includes();
   std::string declare_field(t_field* tfield, bool init=false, bool obj=false);
-  std::string function_signature(t_function* tfunction, std::string prefix="");
+  std::string function_signature(t_function* tfunction, std::string prefix="", bool include_callback=false);
   std::string argument_list(t_struct* tstruct);
   std::string type_to_enum(t_type* ttype);
 
@@ -180,6 +191,20 @@
       return pieces;
   }
 
+  std::string js_type_namespace(t_program* p) {
+    if (gen_node_) {
+      return "ttypes.";
+    }
+    return js_namespace(p);
+  }
+
+  std::string js_export_namespace(t_program* p) {
+    if (gen_node_) {
+      return "exports.";
+    }
+    return js_namespace(p);
+  }
+
   std::string js_namespace(t_program* p) {
       std::string ns = p->get_namespace("js");
       if (ns.size() > 0) {
@@ -193,6 +218,11 @@
  private:
 
   /**
+   * True iff we should generate NodeJS-friendly RPC services.
+   */
+  bool gen_node_;
+
+  /**
    * File streams
    */
   std::ofstream f_types_;
@@ -219,12 +249,16 @@
   // Print header
   f_types_ <<
     autogen_comment() <<
-    js_includes();
+    js_includes() << endl;
 
+  if (gen_node_) {
+    f_types_ << "var ttypes = module.exports = {};" << endl;
+  }
 
   string pns;
 
   //setup the namespace
+  // TODO should the namespace just be in the directory structure for node?
   vector<string> ns_pieces = js_namespace_pieces( program_ );
   if( ns_pieces.size() > 0){
       f_types_ << "var " << ns_pieces[0] << " = {}"<<endl;
@@ -244,6 +278,9 @@
  * Prints standard js imports
  */
 string t_js_generator::js_includes() {
+  if (gen_node_) {
+    return string("var Thrift = require('thrift').Thrift;");
+  }
   string inc;
 
   return inc;
@@ -274,8 +311,7 @@
  * @param tenum The enumeration
  */
 void t_js_generator::generate_enum(t_enum* tenum) {
-
-  f_types_ << js_namespace(tenum->get_program())<<tenum->get_name()<<" = { "<<endl;
+  f_types_ << js_type_namespace(tenum->get_program())<<tenum->get_name()<<" = { "<<endl;
 
   vector<t_enum_value*> constants = tenum->get_constants();
   vector<t_enum_value*>::iterator c_iter;
@@ -298,7 +334,7 @@
   string name = tconst->get_name();
   t_const_value* value = tconst->get_value();
 
-  f_types_ << js_namespace(program_)  << name << " = ";
+  f_types_ << js_type_namespace(program_)  << name << " = ";
   f_types_ << render_const_value(type, value) << endl;
 }
 
@@ -340,7 +376,7 @@
   } else if (type->is_enum()) {
     out << value->get_integer();
   } else if (type->is_struct() || type->is_xception()) {
-    out << "new " << js_namespace(type->get_program()) << type->get_name() << "({" << endl;
+    out << "new " << js_type_namespace(type->get_program()) << type->get_name() << "({" << endl;
     indent_up();
     const vector<t_field*>& fields = ((t_struct*)type)->get_members();
     vector<t_field*>::const_iterator f_iter;
@@ -441,16 +477,34 @@
  */
 void t_js_generator::generate_js_struct_definition(ofstream& out,
                                                        t_struct* tstruct,
-                                                       bool is_exception) {
+                                                       bool is_exception,
+                                                       bool is_exported) {
   const vector<t_field*>& members = tstruct->get_members();
   vector<t_field*>::const_iterator m_iter;
 
-  out <<  js_namespace(tstruct->get_program()) << tstruct->get_name() <<" = function(args){\n";
+  indent_up();
 
+  if (gen_node_) {
+    if (is_exported) {
+      out << "var " << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = " <<
+        "module.exports." << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = function(args){\n";
+    } else {
+      out << "var " << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = function(args){\n";
+    }
+  } else {
+    out << js_namespace(tstruct->get_program()) << tstruct->get_name() <<" = function(args){\n";
+  }
+
+  if (gen_node_ && is_exception) {
+      out << indent() << "Thrift.TException.call(this, \"" <<
+          js_namespace(tstruct->get_program()) << tstruct->get_name() << "\")" << endl;
+      out << indent() << "this.name = \"" <<
+          js_namespace(tstruct->get_program()) << tstruct->get_name() << "\"" << endl;
+  }
 
   //members with arguments
   for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
-    string dval = declare_field(*m_iter,true,true);
+    string dval = declare_field(*m_iter,false,true);
     t_type* t = get_true_type((*m_iter)->get_type());
     if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) {
         dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value());
@@ -487,12 +541,20 @@
   out << "}\n";
 
   if (is_exception) {
-      out << "for (var property in Thrift.Exception)"<<endl<<
-          js_namespace(tstruct->get_program())<<tstruct->get_name()<<"[property] = Thrift.Exception[property]"<<endl;
+      if (gen_node_) {
+          out << "require('util').inherits(" <<
+              js_namespace(tstruct->get_program()) <<
+              tstruct->get_name() << ", Thrift.TException)" << endl;
+      } else {
+          out << "for (var property in Thrift.TException)"<<endl<<
+              js_namespace(tstruct->get_program())<<tstruct->get_name()<<"[property] = Thrift.TException[property]"<<endl;
+      }
   }
 
-  //init prototype
-  out << js_namespace(tstruct->get_program())<<tstruct->get_name() <<".prototype = {}\n";
+  if (!gen_node_) {
+      //init prototype
+      out << js_namespace(tstruct->get_program())<<tstruct->get_name() <<".prototype = {}\n";
+  }
 
 
   generate_js_struct_reader(out, tstruct);
@@ -632,10 +694,23 @@
     string f_service_name = get_out_dir()+service_name_+".js";
     f_service_.open(f_service_name.c_str());
 
+    f_service_ <<
+      autogen_comment() <<
+      js_includes() << endl;
+
+    if (gen_node_) {
+      f_service_ <<
+        "var ttypes = require('./" + program_->get_name() + "_types.js');" << endl;
+    }
+
     generate_service_helpers(tservice);
     generate_service_interface(tservice);
     generate_service_client(tservice);
 
+    if (gen_node_) {
+      generate_service_processor(tservice);
+    }
+
     f_service_.close();
 }
 
@@ -645,7 +720,45 @@
  * @param tservice The service to generate a server for.
  */
 void t_js_generator::generate_service_processor(t_service* tservice) {
-  (void) tservice;
+    vector<t_function*> functions = tservice->get_functions();
+    vector<t_function*>::iterator f_iter;
+
+    f_service_ <<
+        "var " << js_namespace(tservice->get_program()) << service_name_ << "Processor = " <<
+        "exports.Processor = function(handler) ";
+
+    scope_up(f_service_);
+
+    f_service_ << indent() << "this._handler = handler" << endl;
+
+    scope_down(f_service_);
+
+    // Generate the server implementation
+    indent(f_service_) <<
+        js_namespace(tservice->get_program()) << service_name_ << "Processor.prototype.process = function(input, output) ";
+
+    scope_up(f_service_);
+
+    f_service_ << indent() << "var r = input.readMessageBegin()" << endl
+               << indent() << "if (this['process_' + r.fname]) {" << endl
+               << indent() << "  return this['process_' + r.fname].call(this, r.rseqid, input, output)" << endl
+               << indent() << "} else {" << endl
+               << indent() << "  input.skip(Thrift.Type.STRUCT)" << endl
+               << indent() << "  input.readMessageEnd()" << endl
+               << indent() << "  var x = new Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, 'Unknown function ' + r.fname)" << endl
+               << indent() << "  output.writeMessageBegin(r.fname, Thrift.MessageType.Exception, r.rseqid)" << endl
+               << indent() << "  x.write(output)" << endl
+               << indent() << "  output.writeMessageEnd()" << endl
+               << indent() << "  output.flush()" << endl
+               << indent() << "}" << endl;
+
+    scope_down(f_service_);
+    f_service_ << endl;
+
+    // Generate the process subfunctions
+    for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+        generate_process_function(tservice, *f_iter);
+    }
 }
 
 /**
@@ -655,8 +768,70 @@
  */
 void t_js_generator::generate_process_function(t_service* tservice,
                                                  t_function* tfunction) {
-  (void) tservice;
-  (void) tfunction;
+    indent(f_service_) <<
+        js_namespace(tservice->get_program()) << service_name_ << "Processor.prototype.process_" + tfunction->get_name() + " = function(seqid, input, output) ";
+
+    scope_up(f_service_);
+
+    string argsname =  js_namespace(program_)+ service_name_ + "_" + tfunction->get_name() + "_args";
+    string resultname =  js_namespace(program_)+ service_name_ + "_" + tfunction->get_name() + "_result";
+
+    f_service_ <<
+        indent() << "var args = new " << argsname << "()" << endl <<
+        indent() << "args.read(input)" << endl <<
+        indent() << "input.readMessageEnd()" << endl;
+
+    // Declare result for non oneway function
+    if (!tfunction->is_oneway()) {
+        f_service_ <<
+            indent() << "var result = new " << resultname << "()" << endl;
+    }
+
+    // Generate the function call
+    t_struct* arg_struct = tfunction->get_arglist();
+    const std::vector<t_field*>& fields = arg_struct->get_members();
+    vector<t_field*>::const_iterator f_iter;
+
+    f_service_ <<
+      indent() << "this._handler." << tfunction->get_name() << "(";
+
+    bool first = true;
+    for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+      if (first) {
+        first = false;
+      } else {
+        f_service_ << ", ";
+      }
+      f_service_ << "args." << (*f_iter)->get_name();
+    }
+
+    // Shortcut out here for oneway functions
+    if (tfunction->is_oneway()) {
+      f_service_ << ")" << endl;
+      scope_down(f_service_);
+      f_service_ << endl;
+      return;
+    }
+
+    if (!first) {
+        f_service_ << ", ";
+    }
+    f_service_ << "function(success) {" << endl;
+    indent_up();
+
+    f_service_ <<
+      indent() << "result.success = success" << endl <<
+      indent() << "output.writeMessageBegin(\"" << tfunction->get_name() <<
+        "\", Thrift.MessageType.REPLY, seqid)" << endl <<
+      indent() << "result.write(output)" << endl <<
+      indent() << "output.writeMessageEnd()" << endl <<
+      indent() << "output.flush()" << endl;
+
+    indent_down();
+    indent(f_service_) << "})" << endl;
+
+    scope_down(f_service_);
+    f_service_ << endl;
 }
 
 /**
@@ -675,7 +850,7 @@
         t_struct* ts = (*f_iter)->get_arglist();
         string name = ts->get_name();
         ts->set_name(service_name_ + "_" + name);
-        generate_js_struct_definition(f_service_, ts, false);
+        generate_js_struct_definition(f_service_, ts, false, false);
         generate_js_function_helpers(*f_iter);
         ts->set_name(name);
     }
@@ -700,7 +875,7 @@
         result.append(*f_iter);
     }
 
-    generate_js_struct_definition(f_service_, &result, false);
+    generate_js_struct_definition(f_service_, &result, false, false);
 }
 
 /**
@@ -727,16 +902,31 @@
 void t_js_generator::generate_service_client(t_service* tservice) {
   string extends = "";
 
-  f_service_ <<
-      js_namespace(tservice->get_program()) << service_name_ << "Client = function(input, output) {"<<endl;
+  if (gen_node_) {
+    f_service_ <<
+        "var " << js_namespace(tservice->get_program()) << service_name_ << "Client = " << 
+        "exports.Client = function(output, pClass) {"<<endl;
+  } else {
+    f_service_ <<
+        js_namespace(tservice->get_program()) << service_name_ << "Client = function(input, output) {"<<endl;
+  }
 
   indent_up();
 
 
-  f_service_ <<
+  if (gen_node_) {
+    f_service_ <<
+      indent() << "  this.output = output;" << endl <<
+      indent() << "  this.pClass = pClass;" << endl <<
+      indent() << "  this.seqid = 0;" << endl <<
+      indent() << "  this._reqs = {}" << endl;
+  } else {
+    f_service_ <<
       indent() << "  this.input  = input" << endl <<
       indent() << "  this.output = null == output ? input : output" << endl <<
       indent() << "  this.seqid  = 0" << endl;
+  }
+
 
   indent_down();
 
@@ -766,10 +956,16 @@
     string funname = (*f_iter)->get_name();
 
     // Open function
-    f_service_ <<  js_namespace(tservice->get_program())<<service_name_<<"Client.prototype." << function_signature(*f_iter) << "{" << endl;
+    f_service_ <<  js_namespace(tservice->get_program())<<service_name_<<"Client.prototype." << function_signature(*f_iter, "", gen_node_) << "{" << endl;
 
     indent_up();
 
+    if (gen_node_) {
+      f_service_ <<
+        indent() << "this.seqid += 1;" << endl <<
+        indent() << "this._reqs[this.seqid] = callback;" << endl;
+    }
+
     indent(f_service_) << indent() <<
       "this.send_" << funname << "(";
 
@@ -784,7 +980,7 @@
     }
     f_service_ << ")" << endl;
 
-    if (!(*f_iter)->is_oneway()) {
+    if (!gen_node_ && !(*f_iter)->is_oneway()) {
       f_service_ << indent();
       if (!(*f_iter)->get_returntype()->is_void()) {
         f_service_ << "return ";
@@ -802,11 +998,20 @@
 
     indent_up();
 
+    std::string outputVar;
+    if (gen_node_) {
+      f_service_ <<
+        indent() << "var output = new this.pClass(this.output);" << endl;
+      outputVar = "output";
+    } else {
+      outputVar = "this.output";
+    }
+
     std::string argsname =  js_namespace(program_)+ service_name_ + "_" + (*f_iter)->get_name() + "_args";
 
     // Serialize the request header
     f_service_ <<
-      indent() << "this.output.writeMessageBegin('" << (*f_iter)->get_name() << "', Thrift.MessageType.CALL, this.seqid)" << endl;
+      indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() << "', Thrift.MessageType.CALL, this.seqid)" << endl;
 
     f_service_ <<
       indent() << "var args = new " << argsname << "()" << endl;
@@ -818,9 +1023,14 @@
 
     // Write to the stream
     f_service_ <<
-      indent() << "args.write(this.output)" << endl <<
-      indent() << "this.output.writeMessageEnd()" << endl <<
-      indent() << "return this.output.getTransport().flush()" << endl;
+      indent() << "args.write(" << outputVar << ")" << endl <<
+      indent() << outputVar << ".writeMessageEnd()" << endl;
+
+    if (gen_node_) {
+      f_service_ << indent() << "return this.output.flush()" << endl;
+    } else {
+      f_service_ << indent() << "return this.output.getTransport().flush()" << endl;
+    }
 
 
     indent_down();
@@ -830,66 +1040,88 @@
 
     if (!(*f_iter)->is_oneway()) {
       std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_" + (*f_iter)->get_name() + "_result";
-      t_struct noargs(program_);
 
-      t_function recv_function((*f_iter)->get_returntype(),
-                               string("recv_") + (*f_iter)->get_name(),
-                               &noargs);
-      // Open function
-      f_service_ <<
-          endl <<  js_namespace(tservice->get_program())<<service_name_ <<
-          "Client.prototype." << function_signature(&recv_function) << "{" << endl;
+      if (gen_node_) {
+        // Open function
+        f_service_ <<
+            endl <<  js_namespace(tservice->get_program())<<service_name_ <<
+            "Client.prototype.recv_" << (*f_iter)->get_name() << " = function(input,mtype,rseqid){" << endl;
+      } else {
+        t_struct noargs(program_);
+
+        t_function recv_function((*f_iter)->get_returntype(),
+                                 string("recv_") + (*f_iter)->get_name(),
+                                 &noargs);
+        // Open function
+        f_service_ <<
+            endl <<  js_namespace(tservice->get_program())<<service_name_ <<
+            "Client.prototype." << function_signature(&recv_function) << "{" << endl;
+      }
 
       indent_up();
 
-      f_service_ <<
+      std::string inputVar;
+      if (gen_node_) {
+        inputVar = "input";
+      } else {
+        inputVar = "this.input";
+      }
+
+      if (gen_node_) {
+        f_service_ <<
+          indent() << "var callback = this._reqs[rseqid] || function() {};" << endl <<
+          indent() << "delete this._reqs[rseqid];" << endl;
+      } else {
+        f_service_ <<
           indent() << "var ret = this.input.readMessageBegin()" << endl <<
           indent() << "var fname = ret.fname" << endl <<
           indent() << "var mtype = ret.mtype" << endl <<
-          indent() << "var rseqid= ret.rseqid" <<endl <<
+          indent() << "var rseqid= ret.rseqid" <<endl;
+      }
+
+      f_service_ <<
           indent() << "if (mtype == Thrift.MessageType.EXCEPTION) {" << endl <<
-          indent() << "  var x = new Thrift.ApplicationException()" << endl <<
-          indent() << "  x.read(this.input)" << endl <<
-          indent() << "  this.input.readMessageEnd()" << endl <<
-          indent() << "  throw x" << endl <<
+          indent() << "  var x = new Thrift.TApplicationException()" << endl <<
+          indent() << "  x.read(" << inputVar << ")" << endl <<
+          indent() << "  " << inputVar << ".readMessageEnd()" << endl <<
+          indent() << "  " << render_recv_throw("x") << endl <<
           indent() << "}" << endl;
 
 
       f_service_ <<
         indent() << "var result = new " << resultname << "()" << endl <<
-        indent() << "result.read(this.input)" << endl;
+        indent() << "result.read(" << inputVar << ")" << endl;
 
 
       f_service_ <<
-        indent() << "this.input.readMessageEnd()" << endl <<
+        indent() << inputVar << ".readMessageEnd()" << endl <<
         endl;
 
 
-      // Careful, only return result if not a void function
-      if (!(*f_iter)->get_returntype()->is_void()) {
-        f_service_ <<
-          indent() << "if (null != result.success ) {" << endl <<
-          indent() << "  return result.success" << endl <<
-          indent() << "}" << endl;
-      }
-
       t_struct* xs = (*f_iter)->get_xceptions();
       const std::vector<t_field*>& xceptions = xs->get_members();
       vector<t_field*>::const_iterator x_iter;
       for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
         f_service_ <<
           indent() << "if (null != result." << (*x_iter)->get_name() << ") {" << endl <<
-          indent() << "  throw result." << (*x_iter)->get_name() << endl <<
+          indent() << "  " << render_recv_throw("result." + (*x_iter)->get_name()) << endl <<
           indent() << "}" << endl;
       }
 
-      // Careful, only return _result if not a void function
-      if ((*f_iter)->get_returntype()->is_void()) {
-        indent(f_service_) <<
-          "return" << endl;
-      } else {
+      // Careful, only return result if not a void function
+      if (!(*f_iter)->get_returntype()->is_void()) {
         f_service_ <<
-          indent() << "throw \"" << (*f_iter)->get_name() << " failed: unknown result\"" << endl;
+          indent() << "if (null != result.success ) {" << endl <<
+          indent() << "  " << render_recv_return("result.success") << endl <<
+          indent() << "}" << endl;
+        f_service_ <<
+          indent() << render_recv_throw("\"" + (*f_iter)->get_name() + " failed: unknown result\"") << endl;
+      } else {
+          if (gen_node_) {
+            indent(f_service_) << "callback(null)" << endl;
+          } else {
+            indent(f_service_) << "return" << endl;
+          }
       }
 
       // Close function
@@ -901,6 +1133,22 @@
 
 }
 
+std::string t_js_generator::render_recv_throw(std::string var) {
+  if (gen_node_) {
+    return "return callback(" + var + ");";
+  } else {
+    return "throw " + var;
+  }
+}
+
+std::string t_js_generator::render_recv_return(std::string var) {
+  if (gen_node_) {
+    return "return callback(null, " + var + ");";
+  } else {
+    return "return " + var;
+  }
+}
+
 /**
  * Deserializes a field of any type.
  */
@@ -925,7 +1173,7 @@
   } else if (type->is_container()) {
     generate_deserialize_container(out, type, name);
   } else if (type->is_base_type() || type->is_enum()) {
-    indent(out) << "var rtmp = input.";
+    indent(out) << name << " = input.";
 
     if (type->is_base_type()) {
       t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
@@ -961,11 +1209,12 @@
     } else if (type->is_enum()) {
       out << "readI32()";
     }
+
+    if (!gen_node_) {
+        out << ".value";
+    }
+
     out << endl;
-
-    out <<name << " = rtmp.value"<<endl;
-
-
   } else {
     printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n",
            tfield->get_name().c_str(), type->get_name().c_str());
@@ -982,7 +1231,7 @@
                                                    t_struct* tstruct,
                                                    string prefix) {
     out <<
-        indent() << prefix << " = new " <<  js_namespace(tstruct->get_program())<<tstruct->get_name() << "()" << endl <<
+        indent() << prefix << " = new " <<  js_type_namespace(tstruct->get_program())<<tstruct->get_name() << "()" << endl <<
         indent() << prefix << ".read(input)" << endl;
 
 }
@@ -1082,9 +1331,9 @@
   t_field fval(tmap->get_val_type(), val);
 
   indent(out) <<
-    declare_field(&fkey, true, false) << endl;
+    declare_field(&fkey, false, false) << endl;
   indent(out) <<
-    declare_field(&fval, true, false) << endl;
+    declare_field(&fval, false, false) << endl;
 
   generate_deserialize_field(out, &fkey);
   generate_deserialize_field(out, &fval);
@@ -1252,9 +1501,12 @@
     string viter = tmp("viter");
     indent(out) << "for(var "<<kiter<<" in "<<prefix<<")";
     scope_up(out);
+    indent(out) << "if ("<<prefix<<".hasOwnProperty("<<kiter<<"))" <<endl;
+    scope_up(out);
     indent(out) << "var "<<viter<<" = "<<prefix<<"["<<kiter<<"]"<<endl;
     generate_serialize_map_element(out, (t_map*)ttype, kiter, viter);
     scope_down(out);
+    scope_down(out);
 
 
   } else if (ttype->is_set()) {
@@ -1262,9 +1514,12 @@
     indent(out) <<
       "for(var "<<iter<<" in " << prefix << ")" << endl;
     scope_up(out);
+    indent(out) << "if ("<<prefix<<".hasOwnProperty("<<iter<<"))" <<endl;
+    scope_up(out);
     indent(out) << iter << "=" << prefix << "[" << iter << "]"<< endl;
     generate_serialize_set_element(out, (t_set*)ttype, iter);
     scope_down(out);
+    scope_down(out);
 
 
   } else if (ttype->is_list()) {
@@ -1272,9 +1527,12 @@
     indent(out) <<
         "for(var "<<iter<<" in "<< prefix << ")" << endl;
     scope_up(out);
+    indent(out) << "if ("<<prefix<<".hasOwnProperty("<<iter<<"))" <<endl;
+    scope_up(out);
     indent(out) << iter << "=" << prefix << "[" << iter << "]"<< endl;
     generate_serialize_list_element(out, (t_list*)ttype, iter);
     scope_down(out);
+    scope_down(out);
   }
 
   scope_down(out);
@@ -1367,11 +1625,13 @@
       result += " = null";
     } else if (type->is_struct() || type->is_xception()) {
       if (obj) {
-          result += " = new " +js_namespace(type->get_program()) + type->get_name() + "()";
+          result += " = new " +js_type_namespace(type->get_program()) + type->get_name() + "()";
       } else {
         result += " = null";
       }
     }
+  } else {
+    result += " = null";
   }
   return result;
 }
@@ -1383,7 +1643,8 @@
  * @return String of rendered function definition
  */
 string t_js_generator::function_signature(t_function* tfunction,
-                                            string prefix) {
+                                            string prefix,
+                                            bool include_callback) {
 
   string str;
 
@@ -1402,6 +1663,12 @@
       str += (*f_iter)->get_name();
   }
 
+  if (include_callback) {
+    if (!fields.empty()) {
+      str += ",";
+    }
+    str += "callback";
+  }
 
   str += ")";
   return str;
@@ -1469,5 +1736,6 @@
 }
 
 
-THRIFT_REGISTER_GENERATOR(js, "Javascript", "")
+THRIFT_REGISTER_GENERATOR(js, "Javascript", 
+"    node:            Generate node.js compatible code.\n")
 
diff --git a/lib/nodejs/README.md b/lib/nodejs/README.md
new file mode 100644
index 0000000..2832c1b
--- /dev/null
+++ b/lib/nodejs/README.md
@@ -0,0 +1,60 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+
+NOTE: you must use the framed thrift transport, TFramedTransport in most
+implementations, on the server side. Using a popular example, this is enabled
+by default in Cassandra 0.7 (but configuration must be changed in Cassandra
+0.6.x and earlier).
+
+## Install
+
+    npm install thrift 
+
+## Thrift Compiler
+
+You can compile nodejs sources by running the following:
+
+    thrift --gen js:node thrift_file
+
+## Cassandra Client Example:
+
+Here is a Cassandra example:
+
+    var thrift = require('thrift'),
+        Cassandra = require('./gen-nodejs/Cassandra')
+        ttypes = require('./gen-nodejs/cassandra_types');
+
+    var connection = thrift.createConnection("localhost", 9160),
+        client = thrift.createClient(Cassandra, connection);
+
+    connection.on('error', function(err) {
+      console.error(err);
+    });
+
+    client.get_slice("Keyspace", "key", new ttypes.ColumnParent({column_family: "ExampleCF"}), new ttypes.SlicePredicate({slice_range: new ttypes.SliceRange({start: '', finish: ''})}), ttypes.ConsistencyLevel.ONE, function(err, data) {
+      if (err) {
+        // handle err
+      } else {
+        // data == [ttypes.ColumnOrSuperColumn, ...]
+      }
+      connection.end();
+    });
+
+## Custom client and server example
+
+An example based on the one shown on the Thrift front page is included in the examples/ folder.
diff --git a/lib/nodejs/examples/Makefile b/lib/nodejs/examples/Makefile
new file mode 100644
index 0000000..1930279
--- /dev/null
+++ b/lib/nodejs/examples/Makefile
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+ALL:	
+	../../../compiler/cpp/thrift --gen js:node user.thrift
diff --git a/lib/nodejs/examples/README.md b/lib/nodejs/examples/README.md
new file mode 100644
index 0000000..a87581f
--- /dev/null
+++ b/lib/nodejs/examples/README.md
@@ -0,0 +1,29 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# Running the user example
+
+#Generate the bindings:
+../../../compiler/cpp/thrift --gen js:node user.thrift
+
+#To run the user example, first start up the server in one terminal:
+NODE_PATH=../lib:../lib/thrift node server.js
+
+#Now run the client:
+NODE_PATH=../lib:../lib/thrift node client.js
+
+
+    
diff --git a/lib/nodejs/examples/client.js b/lib/nodejs/examples/client.js
new file mode 100644
index 0000000..c83b342
--- /dev/null
+++ b/lib/nodejs/examples/client.js
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 thrift = require('thrift');
+
+var UserStorage = require('./gen-nodejs/UserStorage.js'),
+    ttypes = require('./gen-nodejs/user_types');
+
+var connection = thrift.createConnection('localhost', 9090),
+    client = thrift.createClient(UserStorage, connection);
+
+var user = new ttypes.UserProfile({uid: 1,
+                                   name: "Mark Slee",
+                                   blurb: "I'll find something to put here."});
+
+connection.on('error', function(err) {
+  console.error(err);
+});
+
+client.store(user, function(err, response) {
+  if (err) {
+    console.error(err);
+  } else {
+    console.log("client stored:", user.uid);
+    client.retrieve(user.uid, function(err, responseUser) {
+      if (err) {
+        console.error(err);
+      } else {
+        console.log("client retrieved:", responseUser.uid);
+        connection.end();
+      }
+    });
+  }
+});
diff --git a/lib/nodejs/examples/server.js b/lib/nodejs/examples/server.js
new file mode 100644
index 0000000..3b8c046
--- /dev/null
+++ b/lib/nodejs/examples/server.js
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 thrift = require('thrift');
+
+var UserStorage = require('./gen-nodejs/UserStorage.js'),
+    ttypes = require('./gen-nodejs/user_types');
+
+var users = {};
+
+var server = thrift.createServer(UserStorage, {
+  store: function(user, success) {
+    console.log("server stored:", user.uid);
+    users[user.uid] = user;
+    success();
+  },
+
+  retrieve: function(uid, success) {
+    console.log("server retrieved:", uid);
+    success(users[uid]);
+  },
+});
+
+server.listen(9090);
diff --git a/lib/nodejs/examples/user.thrift b/lib/nodejs/examples/user.thrift
new file mode 100644
index 0000000..ee260e5
--- /dev/null
+++ b/lib/nodejs/examples/user.thrift
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+ 
+struct UserProfile {
+  1: i32 uid,
+  2: string name,
+  3: string blurb
+}
+
+service UserStorage {
+  void store(1: UserProfile user),
+  UserProfile retrieve(1: i32 uid)
+}
diff --git a/lib/nodejs/lib/thrift/binary_parser.js b/lib/nodejs/lib/thrift/binary_parser.js
new file mode 100644
index 0000000..2aeda46
--- /dev/null
+++ b/lib/nodejs/lib/thrift/binary_parser.js
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 sys = require('sys');
+var chr = String.fromCharCode;
+
+var p = exports.BinaryParser = function( bigEndian, allowExceptions ){
+  this.bigEndian = bigEndian || true;
+  this.allowExceptions = allowExceptions;
+};
+p.bigEndian = true;
+
+var IBuffer = exports.BinaryParser.IBuffer = function( bigEndian, buffer ){
+  this.bigEndian = bigEndian || 1;
+  this.buffer = [];
+  this.setBuffer( buffer );
+};
+
+IBuffer.prototype.setBuffer = function( data ){
+  if( data ){
+    for( var l, i = l = data.length, b = this.buffer = new Array( l ); i; b[l - i] = data[--i] );
+    this.bigEndian && b.reverse();
+  }
+};
+
+IBuffer.prototype.hasNeededBits = function( neededBits ){
+  return this.buffer.length >= -( -neededBits >> 3 );
+};
+
+IBuffer.prototype.checkBuffer = function( neededBits ){
+  if( !this.hasNeededBits( neededBits ) ) {
+    console.log("missing: " + neededBits + " " + this.buffer.length);
+    throw new Error( "checkBuffer::missing bytes" );
+  }
+};
+
+IBuffer.prototype.readBits = function( start, length ){
+  //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
+  function shl( a, b ){
+    for( ; b--; a = ( ( a %= 0x7fffffff + 1 ) & 0x40000000 ) == 0x40000000 ? a * 2 : ( a - 0x40000000 ) * 2 + 0x7fffffff + 1 );
+    return a;
+  }
+  if( start < 0 || length <= 0 )
+    return 0;
+  this.checkBuffer( start + length );
+  for( var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - ( start >> 3 ) - 1, lastByte = this.buffer.length + ( -( start + length ) >> 3 ), diff = curByte - lastByte, sum = ( ( this.buffer[ curByte ] >> offsetRight ) & ( ( 1 << ( diff ? 8 - offsetRight : length ) ) - 1 ) ) + ( diff && ( offsetLeft = ( start + length ) % 8 ) ? ( this.buffer[ lastByte++ ] & ( ( 1 << offsetLeft ) - 1 ) ) << ( diff-- << 3 ) - offsetRight : 0 ); diff; sum += shl( this.buffer[ lastByte++ ], ( diff-- << 3 ) - offsetRight ) );
+  return sum;
+};
+  
+p.warn = function( msg ){
+  if( this.allowExceptions )
+    throw new Error( msg );
+  return 1;
+};
+p.decodeFloat = function( data, precisionBits, exponentBits ){
+  var b = new this.IBuffer( this.bigEndian, data );
+  b.checkBuffer( precisionBits + exponentBits + 1 );
+  var bias = Math.pow( 2, exponentBits - 1 ) - 1, signal = b.readBits( precisionBits + exponentBits, 1 ), exponent = b.readBits( precisionBits, exponentBits ), significand = 0,
+  divisor = 2, curByte = b.buffer.length + ( -precisionBits >> 3 ) - 1;
+  do{
+    for( var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 );
+  }while( precisionBits -= startBit );
+  return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 );
+};
+p.decodeInt = function( data, bits, signed, forceBigEndian ){
+  //console.log("decodeInt: ", data, bits, signed);
+  var b = new this.IBuffer( this.bigEndian||forceBigEndian, data ), x = b.readBits( 0, bits ), max = Math.pow( 2, bits );
+  return signed && x >= max / 2 ? x - max : x;
+};
+p.encodeFloat = function( data, precisionBits, exponentBits ){
+  var bias = Math.pow( 2, exponentBits - 1 ) - 1, minExp = -bias + 1, maxExp = bias, minUnnormExp = minExp - precisionBits,
+  status = isNaN( n = parseFloat( data ) ) || n == -Infinity || n == +Infinity ? n : 0,
+  exp = 0, len = 2 * bias + 1 + precisionBits + 3, bin = new Array( len ),
+  signal = ( n = status !== 0 ? 0 : n ) < 0, n = Math.abs( n ), intPart = Math.floor( n ), floatPart = n - intPart,
+  i, lastBit, rounded, j, result;
+  for( i = len; i; bin[--i] = 0 );
+  for( i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor( intPart / 2 ) );
+  for( i = bias + 1; floatPart > 0 && i; ( bin[++i] = ( ( floatPart *= 2 ) >= 1 ) - 0 ) && --floatPart );
+  for( i = -1; ++i < len && !bin[i]; );
+  if( bin[( lastBit = precisionBits - 1 + ( i = ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - ( exp = minExp - 1 ) ) ) + 1] ){
+    if( !( rounded = bin[lastBit] ) ){
+      for( j = lastBit + 2; !rounded && j < len; rounded = bin[j++] );
+    }
+    for( j = lastBit + 1; rounded && --j >= 0; ( bin[j] = !bin[j] - 0 ) && ( rounded = 0 ) );
+  }
+  for( i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i]; );
+  if( ( exp = bias + 1 - i ) >= minExp && exp <= maxExp )
+    ++i;
+  else if( exp < minExp ){
+    exp != bias + 1 - len && exp < minUnnormExp && this.warn( "encodeFloat::float underflow" );
+    i = bias + 1 - ( exp = minExp - 1 );
+  }
+  if( intPart || status !== 0 ){
+    this.warn( intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status );
+    exp = maxExp + 1;
+    i = bias + 2;
+    if( status == -Infinity )
+      signal = 1;
+    else if( isNaN( status ) )
+      bin[i] = 1;
+  }
+  for( n = Math.abs( exp + bias ), j = exponentBits + 1, result = ""; --j; result = ( n % 2 ) + result, n = n >>= 1 );
+  for( n = 0, j = 0, i = ( result = ( signal ? "1" : "0" ) + result + bin.slice( i, i + precisionBits ).join( "" ) ).length, r = []; i; j = ( j + 1 ) % 8 ){
+    n += ( 1 << j ) * result.charAt( --i );
+    if( j == 7 ){
+      r[r.length] = n;
+      n = 0;
+    }
+  }
+  if (n) {
+    r[r.length] = n;
+  }
+  return new Buffer( this.bigEndian ? r.reverse() : r );
+};
+p.encodeInt = function( data, bits, signed, forceBigEndian ){
+  var max = Math.pow( 2, bits );
+  ( data >= max || data < -( max / 2 ) ) && this.warn( "encodeInt::overflow" ) && ( data = 0 );
+  data < 0 && ( data += max );
+  for( var r = []; data; r[r.length] = data % 256, data = Math.floor( data / 256 ) );
+  for( bits = -( -bits >> 3 ) - r.length; bits--; r[r.length] = 0 );
+        return new Buffer((this.bigEndian||forceBigEndian) ? r.reverse() : r );
+};
+p.toSmall    = function( data ){ return this.decodeInt( data,  8, true  ); };
+p.fromSmall  = function( data ){ return this.encodeInt( data,  8, true  ); };
+p.toByte     = function( data ){ return this.decodeInt( data,  8, false ); };
+p.fromByte   = function( data ){ return this.encodeInt( data,  8, false ); };
+p.toShort    = function( data ){ return this.decodeInt( data, 16, true  ); };
+p.fromShort  = function( data ){ return this.encodeInt( data, 16, true  ); };
+p.toWord     = function( data ){ return this.decodeInt( data, 16, false ); };
+p.fromWord   = function( data ){ return this.encodeInt( data, 16, false ); };
+p.toInt      = function( data ){ return this.decodeInt( data, 32, true  ); };
+p.fromInt    = function( data ){ return this.encodeInt( data, 32, true  ); };
+p.toLong     = function( data ){ return this.decodeInt( data, 64, true  ); };
+p.fromLong   = function( data ){ return this.encodeInt( data, 64, true  ); };
+p.toDWord    = function( data ){ return this.decodeInt( data, 32, false ); };
+p.fromDWord  = function( data ){ return this.encodeInt( data, 32, false ); };
+p.toQWord    = function( data ){ return this.decodeInt( data, 64, true ); };
+p.fromQWord  = function( data ){ return this.encodeInt( data, 64, true ); };
+p.toFloat    = function( data ){ return this.decodeFloat( data, 23, 8   ); };
+p.fromFloat  = function( data ){ return this.encodeFloat( data, 23, 8   ); };
+p.toDouble   = function( data ){ return this.decodeFloat( data, 52, 11  ); };
+p.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11  ); };
+
+// Factor out the encode so it can be shared by add_header and push_int32
+p.encode_int32 = function(number) {
+  var a, b, c, d, unsigned;
+  unsigned = (number < 0) ? (number + 0x100000000) : number;
+  a = Math.floor(unsigned / 0xffffff);
+  unsigned &= 0xffffff;
+  b = Math.floor(unsigned / 0xffff);
+  unsigned &= 0xffff;
+  c = Math.floor(unsigned / 0xff);
+  unsigned &= 0xff;
+  d = Math.floor(unsigned);
+  return chr(a) + chr(b) + chr(c) + chr(d);
+};
+
+p.encode_int64 = function(number) {
+  var a, b, c, d, e, f, g, h, unsigned;
+  unsigned = (number < 0) ? (number + 0x10000000000000000) : number;
+  a = Math.floor(unsigned / 0xffffffffffffff);
+  unsigned &= 0xffffffffffffff;
+  b = Math.floor(unsigned / 0xffffffffffff);
+  unsigned &= 0xffffffffffff;
+  c = Math.floor(unsigned / 0xffffffffff);
+  unsigned &= 0xffffffffff;
+  d = Math.floor(unsigned / 0xffffffff);
+  unsigned &= 0xffffffff;
+  e = Math.floor(unsigned / 0xffffff);
+  unsigned &= 0xffffff;
+  f = Math.floor(unsigned / 0xffff);
+  unsigned &= 0xffff;
+  g = Math.floor(unsigned / 0xff);
+  unsigned &= 0xff;
+  h = Math.floor(unsigned);
+  return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h);
+};
+
+/**
+  UTF8 methods
+**/
+
+// Take a raw binary string and return a utf8 string
+p.decode_utf8 = function(a) {
+  var string = "";
+  var i = 0;
+  var c = c1 = c2 = 0;
+
+  while ( i < a.length ) {
+    c = a.charCodeAt(i);
+    if (c < 128) {
+      string += String.fromCharCode(c);
+      i++;
+    } else if((c > 191) && (c < 224)) {
+      c2 = a.charCodeAt(i+1);
+      string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+      i += 2;
+    } else {
+      c2 = a.charCodeAt(i+1);
+      c3 = a.charCodeAt(i+2);
+      string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+      i += 3;
+    }
+  }
+  return string;
+};
+
+// Encode a cstring correctly
+p.encode_cstring = function(s) { 
+  return unescape(encodeURIComponent(s)) + p.fromByte(0);
+};
+
+// Take a utf8 string and return a binary string
+p.encode_utf8 = function(s) {
+  var a="";
+  for (var n=0; n< s.length; n++) {
+    var c=s.charCodeAt(n);
+    if (c<128) {
+      a += String.fromCharCode(c);
+    } else if ((c>127)&&(c<2048)) {
+      a += String.fromCharCode( (c>>6) | 192) ;
+      a += String.fromCharCode( (c&63) | 128);
+    } else {
+      a += String.fromCharCode( (c>>12) | 224);
+      a += String.fromCharCode( ((c>>6) & 63) | 128);
+      a += String.fromCharCode( (c&63) | 128);
+    }
+  }
+  return a;
+};
+
+p.hprint = function(s) {
+  for (var i=0; i<s.length; i++) {
+    if (s.charCodeAt(i)<32) {
+      var number = s.charCodeAt(i) <= 15 ? "0" + s.charCodeAt(i).toString(16) : s.charCodeAt(i).toString(16);
+      sys.debug(number+' : ');}
+    else {
+      var number = s.charCodeAt(i) <= 15 ? "0" + s.charCodeAt(i).toString(16) : s.charCodeAt(i).toString(16);      
+      sys.debug(number+' : '+ s.charAt(i));}
+  }
+};
+
+p.to_byte_array = function(s) {
+  var array = [];
+  
+  for (var i=0; i<s.length; i++) {
+    if (s.charCodeAt(i)<32) {array.push(s.charCodeAt(i));}
+    else {array.push(s.charCodeAt(i))}    
+  }  
+  
+  sys.puts(array);
+}
+
+p.pprint = function(s) {
+  for (var i=0; i<s.length; i++) {
+    if (s.charCodeAt(i)<32) {sys.puts(s.charCodeAt(i)+' : ');}
+    else {sys.puts(s.charCodeAt(i)+' : '+ s.charAt(i));}
+  }
+};
diff --git a/lib/nodejs/lib/thrift/connection.js b/lib/nodejs/lib/thrift/connection.js
new file mode 100644
index 0000000..e469f49
--- /dev/null
+++ b/lib/nodejs/lib/thrift/connection.js
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 sys = require('sys'),
+    EventEmitter = require("events").EventEmitter,
+    net = require('net'),
+    TMemoryBuffer = require('./transport').TMemoryBuffer,
+    TBinaryProtocol = require('./protocol').TBinaryProtocol;
+
+var BinaryParser = require('./binary_parser').BinaryParser;
+BinaryParser.bigEndian = true;
+
+var int32FramedReceiver = exports.int32FramedReceiver = function (callback) {
+  var frameLeft = 0,
+      framePos = 0,
+      frame = null;
+
+  return function(data) {
+    // var buf = new Buffer(data, 'binary');
+    // console.log(buf);
+    // framed transport
+    while (data.length) {
+      if (frameLeft === 0) {
+        // TODO assumes we have all 4 bytes
+        if (data.length < 4) {
+          throw Error("Not enough bytes");
+        }
+        frameLeft = BinaryParser.toInt(data.slice(0,4));
+        frame = new Buffer(frameLeft);
+        framePos = 0;
+        data = data.slice(4, data.length);
+      }
+
+      if (data.length >= frameLeft) {
+        data.copy(frame, framePos, 0, frameLeft);
+        data = data.slice(frameLeft, data.length);
+
+        frameLeft = 0;
+        callback(frame);
+      } else if (data.length) {
+        data.copy(frame, framePos, 0, data.length);
+        frameLeft -= data.length;
+        framePos += data.length;
+        data = data.slice(data.length, data.length);
+      }
+    }
+  };
+}
+
+var Connection = exports.Connection = function(stream, options) {
+  var self = this;
+  EventEmitter.call(this);
+
+  this.connection = stream;
+  this.offline_queue = [];
+  this.options = options || {};
+  this.connected = false;
+
+  this.connection.addListener("connect", function() {
+    self.connected = true;
+    this.setTimeout(self.options.timeout || 0);
+    this.setNoDelay();
+    this.frameLeft = 0;
+    this.framePos = 0;
+    this.frame = null;
+
+    self.offline_queue.forEach(function(data) {
+      self.connection.write(data);
+    });
+
+    self.emit("connect");
+  });
+
+  this.connection.addListener("error", function(err) {
+    self.emit("error", err);
+  });
+
+  // Add a close listener
+  this.connection.addListener("close", function() {
+    self.emit("close");
+  });
+
+  this.connection.addListener("timeout", function() {
+    self.emit("timeout");
+  });
+
+  this.connection.addListener("data", int32FramedReceiver(function(data) {
+    // console.log(typeof(data));
+    var input = new TBinaryProtocol(new TMemoryBuffer(data));
+    var r = input.readMessageBegin();
+    // console.log(r);
+    self.client['recv_' + r.fname](input, r.mtype, r.rseqid);
+    // self.emit("data", data);
+  }));
+}
+sys.inherits(Connection, EventEmitter);
+
+exports.createConnection = function(host, port, options) {
+  var stream = net.createConnection(port, host);
+  var connection = new Connection(stream, options);
+  connection.host = host;
+  connection.port = port;
+
+  return connection;
+}
+
+exports.createClient = function(cls, connection) {
+  if (cls.Client) {
+    cls = cls.Client;
+  }
+  var client = new cls(new TMemoryBuffer(undefined, function(buf) {
+    connection.write(buf);
+  }), TBinaryProtocol);
+
+  // TODO clean this up
+  connection.client = client;
+
+  return client;
+}
+
+Connection.prototype.end = function() {
+  this.connection.end();
+}
+
+Connection.prototype.write = function(buf) {
+  // TODO: optimize this better, allocate one buffer instead of both:
+  var msg = new Buffer(buf.length + 4);
+  BinaryParser.fromInt(buf.length).copy(msg, 0, 0, 4);
+  buf.copy(msg, 4, 0, buf.length);
+  if (!this.connected) {
+    this.offline_queue.push(msg);
+  } else {
+    this.connection.write(msg);
+  }
+}
diff --git a/lib/nodejs/lib/thrift/index.js b/lib/nodejs/lib/thrift/index.js
new file mode 100644
index 0000000..f4fa3cd
--- /dev/null
+++ b/lib/nodejs/lib/thrift/index.js
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+exports.Thrift = require('./thrift');
+
+var connection = require('./connection');
+exports.Connection = connection.Connection;
+exports.createClient = connection.createClient;
+exports.createConnection = connection.createConnection;
+
+exports.createServer = require('./server').createServer;
diff --git a/lib/nodejs/lib/thrift/protocol.js b/lib/nodejs/lib/thrift/protocol.js
new file mode 100644
index 0000000..f333bb9
--- /dev/null
+++ b/lib/nodejs/lib/thrift/protocol.js
@@ -0,0 +1,337 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 sys = require('sys'),
+    Thrift = require('./thrift'),
+    Type = Thrift.Type;
+
+var BinaryParser = require('./binary_parser').BinaryParser;
+BinaryParser.bigEndian = true;
+
+var UNKNOWN = 0,
+    INVALID_DATA = 1,
+    NEGATIVE_SIZE = 2,
+    SIZE_LIMIT = 3,
+    BAD_VERSION = 4;
+
+var TProtocolException = function(type, message) {
+  Error.call(this, message);
+  this.name = 'TProtocolException';
+  this.type = type;
+}
+sys.inherits(TProtocolException, Error);
+
+var TBinaryProtocol = exports.TBinaryProtocol = function(trans, strictRead, strictWrite) {
+  this.trans = trans;
+  this.strictRead = (strictRead !== undefined ? strictRead : false);
+  this.strictWrite = (strictWrite !== undefined ? strictWrite : true);
+}
+
+TBinaryProtocol.prototype.flush = function() {
+  return this.trans.flush();
+}
+
+// NastyHaxx. JavaScript forces hex constants to be
+// positive, converting this into a long. If we hardcode the int value
+// instead it'll stay in 32 bit-land.
+
+var VERSION_MASK = -65536, // 0xffff0000
+    VERSION_1 = -2147418112, // 0x80010000
+    TYPE_MASK = 0x000000ff;
+
+TBinaryProtocol.prototype.writeMessageBegin = function(name, type, seqid) {
+    if (this.strictWrite) {
+      this.writeI32(VERSION_1 | type);
+      this.writeString(name);
+      this.writeI32(seqid);
+    } else {
+      this.writeString(name);
+      this.writeByte(type);
+      this.writeI32(seqid);
+    }
+}
+
+TBinaryProtocol.prototype.writeMessageEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeStructBegin = function(name) {
+}
+
+TBinaryProtocol.prototype.writeStructEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeFieldBegin = function(name, type, id) {
+  this.writeByte(type);
+  this.writeI16(id);
+}
+
+TBinaryProtocol.prototype.writeFieldEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeFieldStop = function() {
+  this.writeByte(Type.STOP);
+}
+
+TBinaryProtocol.prototype.writeMapBegin = function(ktype, vtype, size) {
+  this.writeByte(ktype);
+  this.writeByte(vtype);
+  this.writeI32(size);
+}
+
+TBinaryProtocol.prototype.writeMapEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeListBegin = function(etype, size) {
+  this.writeByte(etype);
+  this.writeI32(size);
+}
+
+TBinaryProtocol.prototype.writeListEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeSetBegin = function(etype, size) {
+  console.log('write set', etype, size);
+  this.writeByte(etype);
+  this.writeI32(size);
+}
+
+TBinaryProtocol.prototype.writeSetEnd = function() {
+}
+
+TBinaryProtocol.prototype.writeBool = function(bool) {
+  if (bool) {
+    this.writeByte(1);
+  } else {
+    this.writeByte(0);
+  }
+}
+
+TBinaryProtocol.prototype.writeByte = function(byte) {
+  this.trans.write(BinaryParser.fromByte(byte));
+}
+
+TBinaryProtocol.prototype.writeI16 = function(i16) {
+  this.trans.write(BinaryParser.fromShort(i16));
+}
+
+TBinaryProtocol.prototype.writeI32 = function(i32) {
+  this.trans.write(BinaryParser.fromInt(i32));
+}
+
+TBinaryProtocol.prototype.writeI64 = function(i64) {
+  this.trans.write(BinaryParser.fromLong(i64));
+}
+
+TBinaryProtocol.prototype.writeDouble = function(dub) {
+  this.trans.write(BinaryParser.fromDouble(dub));
+}
+
+TBinaryProtocol.prototype.writeString = function(str) {
+  this.writeI32(str.length);
+  this.trans.write(str);
+}
+
+TBinaryProtocol.prototype.readMessageBegin = function() {
+  var sz = this.readI32();
+  var type, name, seqid;
+
+  if (sz < 0) {
+    var version = sz & VERSION_MASK;
+    if (version != VERSION_1) {
+      console.log("BAD: " + version);
+      throw TProtocolException(BAD_VERSION, "Bad version in readMessageBegin: " + sz);
+    }
+    type = sz & TYPE_MASK;
+    name = this.readString();
+    seqid = this.readI32();
+  } else {
+    if (this.strictRead) {
+      throw TProtocolException(BAD_VERSION, "No protocol version header");
+    }
+    name = this.trans.read(sz);
+    type = this.readByte();
+    seqid = this.readI32();
+  }
+  return {fname: name, mtype: type, rseqid: seqid};
+}
+
+TBinaryProtocol.prototype.readMessageEnd = function() {
+}
+
+TBinaryProtocol.prototype.readStructBegin = function() {
+  return {fname: ''}
+}
+
+TBinaryProtocol.prototype.readStructEnd = function() {
+}
+
+TBinaryProtocol.prototype.readFieldBegin = function() {
+  var type = this.readByte();
+  if (type == Type.STOP) {
+    return {fname: null, ftype: type, fid: 0};
+  }
+  var id = this.readI16();
+  return {fname: null, ftype: type, fid: id};
+}
+
+TBinaryProtocol.prototype.readFieldEnd = function() {
+}
+
+TBinaryProtocol.prototype.readMapBegin = function() {
+  var ktype = this.readByte();
+  var vtype = this.readByte();
+  var size = this.readI32();
+  return {ktype: ktype, vtype: vtype, size: size};
+}
+
+TBinaryProtocol.prototype.readMapEnd = function() {
+}
+
+TBinaryProtocol.prototype.readListBegin = function() {
+  var etype = this.readByte();
+  var size = this.readI32();
+  return {etype: etype, size: size};
+}
+
+TBinaryProtocol.prototype.readListEnd = function() {
+}
+
+TBinaryProtocol.prototype.readSetBegin = function() {
+  var etype = this.readByte();
+  var size = this.readI32();
+  return {etype: etype, size: size};
+}
+
+TBinaryProtocol.prototype.readSetEnd = function() {
+}
+
+TBinaryProtocol.prototype.readBool = function() {
+  var byte = this.readByte();
+  if (byte == 0) {
+    return false;
+  }
+  return true;
+}
+
+TBinaryProtocol.prototype.readByte = function() {
+  var buff = this.trans.read(1);
+  return BinaryParser.toByte(buff);
+}
+
+TBinaryProtocol.prototype.readI16 = function() {
+  var buff = this.trans.read(2);
+  return BinaryParser.toShort(buff);
+}
+
+TBinaryProtocol.prototype.readI32 = function() {
+  var buff = this.trans.read(4);
+  // console.log("read buf: ");
+  // console.log(buff);
+  // console.log("result: " + BinaryParser.toInt(buff));
+  return BinaryParser.toInt(buff);
+}
+
+TBinaryProtocol.prototype.readI64 = function() {
+  var buff = this.trans.read(8);
+  return BinaryParser.toLong(buff);
+}
+
+TBinaryProtocol.prototype.readDouble = function() {
+  var buff = this.trans.read(8);
+  return BinaryParser.toFloat(buff);
+}
+
+TBinaryProtocol.prototype.readBinary = function() {
+  var len = this.readI32();
+  return this.trans.read(len);
+}
+
+TBinaryProtocol.prototype.readString = function() {
+  var r = this.readBinary().toString('binary');
+  // console.log("readString: " + r);
+  return r;
+}
+
+TBinaryProtocol.prototype.getTransport = function() {
+  return this.trans;
+}
+
+TBinaryProtocol.prototype.skip = function(type) {
+  // console.log("skip: " + type);
+  switch (type) {
+    case Type.STOP:
+      return;
+    case Type.BOOL:
+      this.readBool();
+      break;
+    case Type.BYTE:
+      this.readByte();
+      break;
+    case Type.I16:
+      this.readI16();
+      break;
+    case Type.I32:
+      this.readI32();
+      break;
+    case Type.I64:
+      this.readI64();
+      break;
+    case Type.DOUBLE:
+      this.readDouble();
+      break;
+    case Type.STRING:
+      this.readString();
+      break;
+    case Type.STRUCT:
+      this.readStructBegin();
+      while (true) {
+        var r = this.readFieldBegin();
+        if (r.ftype === Type.STOP) {
+          break;
+        }
+        this.skip(r.ftype);
+        this.readFieldEnd();
+      }
+      this.readStructEnd();
+      break;
+    case Type.MAP:
+      var r = this.readMapBegin();
+      for (var i = 0; i < r.size; ++i) {
+        this.skip(r.ktype);
+        this.skip(r.vtype);
+      }
+      this.readMapEnd();
+      break;
+    case Type.SET:
+      var r = this.readSetBegin();
+      for (var i = 0; i < r.size; ++i) {
+        this.skip(r.etype);
+      }
+      this.readSetEnd();
+      break;
+    case Type.LIST:
+      var r = this.readListBegin();
+      for (var i = 0; i < r.size; ++i) {
+        this.skip(r.etype);
+      }
+      this.readListEnd();
+      break;
+    default:
+      throw Error("Invalid type: " + type);
+  }
+}
diff --git a/lib/nodejs/lib/thrift/server.js b/lib/nodejs/lib/thrift/server.js
new file mode 100644
index 0000000..43a7967
--- /dev/null
+++ b/lib/nodejs/lib/thrift/server.js
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 sys = require('sys'),
+    net = require('net');
+
+var BinaryParser = require('./binary_parser').BinaryParser,
+    TMemoryBuffer = require('./transport').TMemoryBuffer,
+    TBinaryProtocol = require('./protocol').TBinaryProtocol,
+    int32FramedReceiver = require('./connection').int32FramedReceiver;
+
+exports.createServer = function(cls, handler) {
+  if (cls.Processor) {
+    cls = cls.Processor;
+  }
+  var processor = new cls(handler);
+
+  return net.createServer(function(stream) {
+    stream.on('data', int32FramedReceiver(function(data) {
+      var input = new TBinaryProtocol(new TMemoryBuffer(data));
+      var output = new TBinaryProtocol(new TMemoryBuffer(undefined, function(buf) {
+        // TODO: optimize this better, allocate one buffer instead of both:
+        var msg = new Buffer(buf.length + 4);
+        BinaryParser.fromInt(buf.length).copy(msg, 0, 0, 4);
+        buf.copy(msg, 4, 0, buf.length);
+        stream.write(msg);
+      }));
+
+      processor.process(input, output);
+    }));
+
+    stream.on('end', function() {
+      stream.end();
+    });
+  });
+}
diff --git a/lib/nodejs/lib/thrift/thrift.js b/lib/nodejs/lib/thrift/thrift.js
new file mode 100644
index 0000000..73f772b
--- /dev/null
+++ b/lib/nodejs/lib/thrift/thrift.js
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 sys = require('sys');
+
+var Type = exports.Type = {
+  STOP: 0,
+  VOID: 1,
+  BOOL: 2,
+  BYTE: 3,
+  I08: 3,
+  DOUBLE: 4,
+  I16: 6,
+  I32: 8,
+  I64: 10,
+  STRING: 11,
+  UTF7: 11,
+  STRUCT: 12,
+  MAP: 13,
+  SET: 14,
+  LIST: 15,
+  UTF8: 16,
+  UTF16: 17,
+}
+
+exports.MessageType = {
+  CALL: 1,
+  REPLY: 2,
+  EXCEPTION: 3,
+  ONEWAY: 4,
+}
+
+var TException = exports.TException = function(message) {
+  Error.call(this, message);
+  this.name = 'TException';
+}
+sys.inherits(TException, Error);
+
+var TApplicationExceptionType = exports.TApplicationExceptionType = {
+  UNKNOWN: 0,
+  UNKNOWN_METHOD: 1,
+  INVALID_MESSAGE_TYPE: 2,
+  WRONG_METHOD_NAME: 3,
+  BAD_SEQUENCE_ID: 4,
+  MISSING_RESULT: 5
+}
+
+var TApplicationException = exports.TApplicationException = function(type, message) {
+  TException.call(this, message);
+  this.type = type || TApplicationExceptionType.UNKNOWN;
+  this.name = 'TApplicationException';
+}
+sys.inherits(TApplicationException, TException);
+
+TApplicationException.prototype.read = function(input) {
+  var ftype
+  var fid
+  var ret = input.readStructBegin('TApplicationException')
+
+  while(1){
+
+      ret = input.readFieldBegin()
+
+      if(ret.ftype == Type.STOP)
+          break
+
+      var fid = ret.fid
+
+      switch(fid){
+          case 1:
+              if( ret.ftype == Type.STRING ){
+                  ret = input.readString()
+                  this.message = ret.value
+              } else {
+                  ret = input.skip(ret.ftype)
+              }
+
+              break
+          case 2:
+              if( ret.ftype == Type.I32 ){
+                  ret = input.readI32()
+                  this.type = ret.value
+              } else {
+                  ret   = input.skip(ret.ftype)
+              }
+              break
+
+          default:
+              ret = input.skip(ret.ftype)
+              break
+      }
+      input.readFieldEnd()
+  }
+
+  input.readStructEnd()
+}
+
+TApplicationException.prototype.write = function(output){
+  output.writeStructBegin('TApplicationException');
+
+  if (this.message) {
+      output.writeFieldBegin('message', Type.STRING, 1)
+      output.writeString(this.message)
+      output.writeFieldEnd()
+  }
+
+  if (this.code) {
+      output.writeFieldBegin('type', Type.I32, 2)
+      output.writeI32(this.code)
+      output.writeFieldEnd()
+  }
+
+  output.writeFieldStop()
+  output.writeStructEnd()
+}
diff --git a/lib/nodejs/lib/thrift/transport.js b/lib/nodejs/lib/thrift/transport.js
new file mode 100644
index 0000000..0c10ef0
--- /dev/null
+++ b/lib/nodejs/lib/thrift/transport.js
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 TMemoryBuffer = exports.TMemoryBuffer = function(buffer, flushCallback) {
+  if (buffer !== undefined) {
+    this.recv_buf = buffer;
+  } else {
+    this.recv_buf = new Buffer(0);
+  }
+  this.recv_buf_sz = this.recv_buf.length;
+  this.send_buf = [];
+  this.rpos = 0;
+  this.flushCallback = flushCallback;
+}
+
+TMemoryBuffer.prototype.isOpen = function() {
+  // TODO
+  return true;
+}
+
+TMemoryBuffer.prototype.open = function() {
+}
+
+TMemoryBuffer.prototype.close = function() {
+}
+
+TMemoryBuffer.prototype.read = function(len) {
+  var avail = this.recv_buf_sz - this.rpos;
+  // console.log("avail: " + avail);
+
+  if(avail == 0)
+      return new Buffer(0);
+
+  var give = len
+
+  if(avail < len) {
+      console.log("asked for: " + len);
+      throw new Error("asked for too much");
+      give = avail
+  }
+
+  // console.log(this.rpos + "," + give);
+  var ret = this.recv_buf.slice(this.rpos,this.rpos + give)
+  this.rpos += give
+  // console.log(ret);
+
+  //clear buf when complete?
+  return ret
+
+}
+
+TMemoryBuffer.prototype.readAll = function() {
+  return this.recv_buf;
+}
+
+TMemoryBuffer.prototype.write = function(buf) {
+  // TODO
+  if (typeof(buf) === "string") {
+    for (var i = 0; i < buf.length; ++i) {
+      this.send_buf.push(buf.charCodeAt(i));
+    }
+  } else {
+    for (var i = 0; i < buf.length; ++i) {
+      this.send_buf.push(buf[i]);
+    }
+  }
+}
+
+TMemoryBuffer.prototype.flush = function() {
+  this.flushCallback(new Buffer(this.send_buf));
+  this.send_buf = [];
+}
diff --git a/lib/nodejs/package.json b/lib/nodejs/package.json
new file mode 100644
index 0000000..e6ebca9
--- /dev/null
+++ b/lib/nodejs/package.json
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+{
+  "name": "thrift",
+  "description": "node-thrift",
+  "version": "0.6.0",
+  "author": "Apache Thrift",
+  "directories" : { "lib" : "./lib/thrift" },
+  "main": "./lib/thrift",
+  "engines": { "node": ">= 0.2.4" },
+}