UNPKG

poseidon

Version:

Simplify Node Callback APIs with an optimized promise layer that doesnt compromise on performance.

183 lines (163 loc) 7.72 kB
esprima = require 'esprima' escodegen = require 'escodegen' Promise = require 'bluebird' fs = require 'fs' class Poseidon constructor:(configuration = {}) -> @configuration = configuration return generate: (path) -> files = [] for className, classSchema of @configuration file = {} file.name = "#{className.toLowerCase()}.js" code = file.code = [] dependencies = ["var Promise = require('bluebird');"] for dependency, dependencyPath of classSchema.require dependencies.push "var #{dependency} = require('#{dependencyPath}');" # Generate class constructor code.push """ function #{className}(#{classSchema.constructor.params.join(", ")}) { #{classSchema.constructor.body} } """ for functionName, functionSchema of classSchema.functions # Set default options functionSchema.wrap ?= true functionSchema.params ?= [] # Generate class method hunk = [""" #{className}.prototype.#{functionName} = function (#{if functionSchema.body? then "#{functionSchema.params.join(", ")}){\n" else "){\n var args = arguments;"} """] if classSchema.type is 'promise' instanceIdentifier = "instanceValue" else instanceIdentifier = "this.instance" # Use custom body if specified if functionSchema.body? hunk.push(functionSchema.body) else # Create deferred and callback if wrap option is true if functionSchema.wrap or classSchema.type is 'promise' hunk.push "var deferred = Promise.pending();" if not functionSchema.wrap hunk.push "var result;" if classSchema.type is 'promise' hunk.push """ this.instance.catch(function(err){ deferred.reject(err); return deferred.promise; }).then(function (instanceValue) { """ if functionSchema.wrap # Code to wrap return values castValues = [] if functionSchema.return? for index, constructorName of functionSchema.return if constructorName isnt null if constructorName instanceof Object if constructorName.array? castValues.push "arguments[#{parseInt(index)+1}] = arguments[#{parseInt(index)+1}].map(function(item) { return new #{constructorName.name}(item); });" else castValues.push "arguments[#{parseInt(index)+1}] = new #{constructorName}(arguments[#{parseInt(index)+1}]);" hunk.push """ var callback = function () { if (arguments[0]) { if (arguments.length === 1 || arguments[1] == null) { deferred.reject(arguments[0]); } else { deferred.reject(Array.prototype.slice.call(arguments, 0)); } } else { #{castValues.join("\n")} switch(arguments.length) { case 0: deferred.resolve(); break; case 2: deferred.resolve(arguments[1]); break; case 3: deferred.resolve([arguments[1], arguments[2]]); break; case 4: deferred.resolve([arguments[1], arguments[2], arguments[3]]); break; case 5: deferred.resolve([arguments[1], arguments[2], arguments[3], arguments[4]]); break; case 6: deferred.resolve([arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]]); break; default: deferred.resolve(Array.prototype.slice.call(null, arguments, 1)); break; } } }; """ # Generate optimized function call hunk.push """ switch(args.length) { case 0: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(#{if functionSchema.wrap then "callback" else ""}); break; case 1: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(args[0]#{if functionSchema.wrap then ", callback" else ""}); break; case 2: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(args[0], args[1]#{if functionSchema.wrap then ", callback" else ""}); break; case 3: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(args[0], args[1], args[2]#{if functionSchema.wrap then ", callback" else ""}); break; case 4: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(args[0], args[1], args[2], args[3]#{if functionSchema.wrap then ", callback" else ""}); break; case 5: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}(args[0], args[1], args[2], args[3], args[4]#{if functionSchema.wrap then ", callback" else ""}); break; default: #{if functionSchema.wrap then "" else "result = "}#{instanceIdentifier}.#{functionName}.apply(#{instanceIdentifier}, #{if functionSchema.wrap then "Array.prototype.slice.call(null, args).concat(callback)" else "args"}); break; } #{if not functionSchema.wrap and classSchema.type is 'promise' and functionSchema.return? then "result = new #{functionSchema.return[0]}(result);" else ""} #{if not functionSchema.wrap and classSchema.type is 'promise' then "deferred.resolve(result);" else ""} """ if classSchema.type is 'promise' hunk.push "});" # Return promise if wrap option is set if functionSchema.wrap or classSchema.type is 'promise' if not functionSchema.wrap and functionSchema.return?.length > 1 then throw new Error("Only 1 return value allowed when no callback is present") hunk.push "return deferred.promise;" else # If no wrap option is allowed only 1 value can be set in return if functionSchema.return? if functionSchema.return.length > 1 then throw new Error("Only 1 return value allowed when no callback is present") hunk.push "return new #{functionSchema.return[0]}(result);" else if functionSchema.chain hunk.push "return this;" else hunk.push "return result;" # End of function call hunk.push "};" code.push hunk.join("\n") code.push "module.exports = #{className};" try # Parse code to ensure generated javascript is valid code = "#{dependencies.join("\n")}\n#{code.join("\n")}" # Pretty format code file.code = escodegen.generate(esprima.parse(code)) files.push file catch err codeLines = code.split("\n") throw new Error("Error generating class #{className}\n#{err.message}\n#{codeLines[err.lineNumber-1]}") if path? write = Promise.promisify(fs.writeFile, fs) Promise.map files, (file) -> write("#{path}/#{file.name}", file.code) .then -> Promise.resolve files else files module.exports = Poseidon