Source: lib/index.js

// Copyright (c) 2014 Quildreen Motta
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
 * Monadic wrapper over Express web framework.
 *
 * @module express
 */
// -- Dependencies -----------------------------------------------------
var adt = require('adt-simple');
var curry = require('core.lambda').curry;
var compose = require('core.lambda').compose;
var Future = require('data.future');
var extend = require('xtend');
var methods = require('methods');
var destructiveExtend = require('xtend/mutable');
// -- Data structures --------------------------------------------------
/**
 * Represents a server's configuration.
 *
 * Servers are made up of several components, which are stacked on top
 * of each other in the order they're given. Components that are
 * installed first have precedence, and are executed first whenever
 * a handler for a particular route is called.
 *
 * @class
 * @summary
 * | Setting: { name: String, value: Any }
 * | Plugin:  { path: String, handler: (Request, ExpressRes, (Void → Void) → Void) }
 * | Route:   { method: String, spec: String|RegExp, handler: (Request → Future[Error, Response]) }
 * | Engine:  { extension: String, engine: (Path, Object, (Error, String → Void) → Void) }
 */
var Component = function () {
        function Component$2() {
        }
        function Setting$2(name, value) {
            if (!(this instanceof Setting$2)) {
                return new Setting$2(name, value);
            }
            if (typeof name === 'string' || Object.prototype.toString.call(name) === '[object String]') {
                this.name = name;
            } else {
                throw new TypeError('Unexpected type for field: Component.Setting.name');
            }
            this.value = value;
        }
        Setting$2.prototype = new Component$2();
        Setting$2.prototype.constructor = Setting$2;
        function Plugin$2(path, handler) {
            if (!(this instanceof Plugin$2)) {
                return new Plugin$2(path, handler);
            }
            if (typeof path === 'string' || Object.prototype.toString.call(path) === '[object String]') {
                this.path = path;
            } else {
                throw new TypeError('Unexpected type for field: Component.Plugin.path');
            }
            if (Object.prototype.toString.call(handler) === '[object Function]') {
                this.handler = handler;
            } else {
                throw new TypeError('Unexpected type for field: Component.Plugin.handler');
            }
        }
        Plugin$2.prototype = new Component$2();
        Plugin$2.prototype.constructor = Plugin$2;
        function Route$2(method, spec, handler) {
            if (!(this instanceof Route$2)) {
                return new Route$2(method, spec, handler);
            }
            if (typeof method === 'string' || Object.prototype.toString.call(method) === '[object String]') {
                this.method = method;
            } else {
                throw new TypeError('Unexpected type for field: Component.Route.method');
            }
            this.spec = spec;
            if (Object.prototype.toString.call(handler) === '[object Function]') {
                this.handler = handler;
            } else {
                throw new TypeError('Unexpected type for field: Component.Route.handler');
            }
        }
        Route$2.prototype = new Component$2();
        Route$2.prototype.constructor = Route$2;
        function Engine$2(extension, engine) {
            if (!(this instanceof Engine$2)) {
                return new Engine$2(extension, engine);
            }
            if (typeof extension === 'string' || Object.prototype.toString.call(extension) === '[object String]') {
                this.extension = extension;
            } else {
                throw new TypeError('Unexpected type for field: Component.Engine.extension');
            }
            if (Object.prototype.toString.call(engine) === '[object Function]') {
                this.engine = engine;
            } else {
                throw new TypeError('Unexpected type for field: Component.Engine.engine');
            }
        }
        Engine$2.prototype = new Component$2();
        Engine$2.prototype.constructor = Engine$2;
        function Locals$2(value) {
            if (!(this instanceof Locals$2)) {
                return new Locals$2(value);
            }
            this.value = value;
        }
        Locals$2.prototype = new Component$2();
        Locals$2.prototype.constructor = Locals$2;
        var derived = adt.Cata.derive(adt.Base.derive({
                name: 'Component',
                constructor: Component$2,
                prototype: Component$2.prototype,
                variants: [
                    {
                        name: 'Setting',
                        constructor: Setting$2,
                        prototype: Setting$2.prototype,
                        fields: [
                            'name',
                            'value'
                        ]
                    },
                    {
                        name: 'Plugin',
                        constructor: Plugin$2,
                        prototype: Plugin$2.prototype,
                        fields: [
                            'path',
                            'handler'
                        ]
                    },
                    {
                        name: 'Route',
                        constructor: Route$2,
                        prototype: Route$2.prototype,
                        fields: [
                            'method',
                            'spec',
                            'handler'
                        ]
                    },
                    {
                        name: 'Engine',
                        constructor: Engine$2,
                        prototype: Engine$2.prototype,
                        fields: [
                            'extension',
                            'engine'
                        ]
                    },
                    {
                        name: 'Locals',
                        constructor: Locals$2,
                        prototype: Locals$2.prototype,
                        fields: ['value']
                    }
                ]
            }));
        Component$2.Setting = derived.variants[0].constructor;
        Component$2.Plugin = derived.variants[1].constructor;
        Component$2.Route = derived.variants[2].constructor;
        Component$2.Engine = derived.variants[3].constructor;
        Component$2.Locals = derived.variants[4].constructor;
        return Component$2;
    }();
var Setting = Component.Setting;
var Plugin = Component.Plugin;
var Route = Component.Route;
var Engine = Component.Engine;
var Locals = Component.Locals;
/**
 * Represents the response from a route handler.
 *
 * @class
 * @summary
 * | Redirect: { url: URL }
 * | Render:   { view: Path, options: Object }
 * | Send:     { status: Number, headers: Object, body: Content }
 */
var Response = function () {
        function Response$2() {
        }
        function Redirect$2(url) {
            if (!(this instanceof Redirect$2)) {
                return new Redirect$2(url);
            }
            this.url = url;
        }
        Redirect$2.prototype = new Response$2();
        Redirect$2.prototype.constructor = Redirect$2;
        function Render$2(view, options) {
            if (!(this instanceof Render$2)) {
                return new Render$2(view, options);
            }
            if (typeof view === 'string' || Object.prototype.toString.call(view) === '[object String]') {
                this.view = view;
            } else {
                throw new TypeError('Unexpected type for field: Response.Render.view');
            }
            this.options = options;
        }
        Render$2.prototype = new Response$2();
        Render$2.prototype.constructor = Render$2;
        function Send$2(status, headers, body) {
            if (!(this instanceof Send$2)) {
                return new Send$2(status, headers, body);
            }
            if (typeof status === 'number' || Object.prototype.toString.call(status) === '[object Number]') {
                this.status = status;
            } else {
                throw new TypeError('Unexpected type for field: Response.Send.status');
            }
            this.headers = headers;
            if (body instanceof Content) {
                this.body = body;
            } else {
                throw new TypeError('Unexpected type for field: Response.Send.body');
            }
        }
        Send$2.prototype = new Response$2();
        Send$2.prototype.constructor = Send$2;
        var derived = adt.Cata.derive(adt.Base.derive({
                name: 'Response',
                constructor: Response$2,
                prototype: Response$2.prototype,
                variants: [
                    {
                        name: 'Redirect',
                        constructor: Redirect$2,
                        prototype: Redirect$2.prototype,
                        fields: ['url']
                    },
                    {
                        name: 'Render',
                        constructor: Render$2,
                        prototype: Render$2.prototype,
                        fields: [
                            'view',
                            'options'
                        ]
                    },
                    {
                        name: 'Send',
                        constructor: Send$2,
                        prototype: Send$2.prototype,
                        fields: [
                            'status',
                            'headers',
                            'body'
                        ]
                    }
                ]
            }));
        Response$2.Redirect = derived.variants[0].constructor;
        Response$2.Render = derived.variants[1].constructor;
        Response$2.Send = derived.variants[2].constructor;
        return Response$2;
    }();
var Redirect = Response.Redirect;
var Render = Response.Render;
var Send = Response.Send;
/**
 * Represents the body of a response.
 *
 * @class
 * @summary
 * | Buffer: { value: Buffer }
 * | Text:   { value: String }
 * | Value:  { value: Any }
 */
var Content = function () {
        function Content$2() {
        }
        function Buffer$2(value) {
            if (!(this instanceof Buffer$2)) {
                return new Buffer$2(value);
            }
            this.value = value;
        }
        Buffer$2.prototype = new Content$2();
        Buffer$2.prototype.constructor = Buffer$2;
        function Text$2(value) {
            if (!(this instanceof Text$2)) {
                return new Text$2(value);
            }
            this.value = value;
        }
        Text$2.prototype = new Content$2();
        Text$2.prototype.constructor = Text$2;
        function Value$2(value) {
            if (!(this instanceof Value$2)) {
                return new Value$2(value);
            }
            this.value = value;
        }
        Value$2.prototype = new Content$2();
        Value$2.prototype.constructor = Value$2;
        var derived = adt.Cata.derive(adt.Base.derive({
                name: 'Content',
                constructor: Content$2,
                prototype: Content$2.prototype,
                variants: [
                    {
                        name: 'Buffer',
                        constructor: Buffer$2,
                        prototype: Buffer$2.prototype,
                        fields: ['value']
                    },
                    {
                        name: 'Text',
                        constructor: Text$2,
                        prototype: Text$2.prototype,
                        fields: ['value']
                    },
                    {
                        name: 'Value',
                        constructor: Value$2,
                        prototype: Value$2.prototype,
                        fields: ['value']
                    }
                ]
            }));
        Content$2.Buffer = derived.variants[0].constructor;
        Content$2.Text = derived.variants[1].constructor;
        Content$2.Value = derived.variants[2].constructor;
        return Content$2;
    }();
var Buffer = Content.Buffer;
var Text = Content.Text;
var Value = Content.Value;
// -- Helpers ----------------------------------------------------------
/**
 * Applies a configuration to an express application.
 *
 * @private
 * @summary App, Component → App
 */
function configure(app, route) {
    (function (a0) {
        var r13 = Setting.unapply(a0);
        if (r13 != null && r13.length === 2) {
            var r14 = r13[0];
            var r15 = r13[1];
            var name = r14, value = r15;
            return app.set(name, value);
        }
        var r16 = Plugin.unapply(a0);
        if (r16 != null && r16.length === 2) {
            var r17 = r16[0];
            var r18 = r16[1];
            var mountPoint = r17, handler = r18;
            return app.use(mountPoint, handler);
        }
        var r19 = Engine.unapply(a0);
        if (r19 != null && r19.length === 2) {
            var r20 = r19[0];
            var r21 = r19[1];
            var extension = r20, engine = r21;
            return app.engine(extension, engine);
        }
        var r22 = Locals.unapply(a0);
        if (r22 != null && r22.length === 1) {
            var r23 = r22[0];
            var object = r23;
            return destructiveExtend(app.locals, object);
        }
        var r24 = Route.unapply(a0);
        if (r24 != null && r24.length === 3) {
            var r25 = r24[0];
            var r26 = r24[1];
            var r27 = r24[2];
            var method = r25, spec = r26, handler = r27;
            return handleRequest(app, method, spec, handler);
        }
        throw new TypeError('No match');
    }.call(this, route));
    return app;
}
/**
 * Setups a request handler for a particular route-spec.
 *
 * @private
 * @summary App, Method, RouteSpec, (Request → Future[Error, Repsonse]) → App
 * : where RouteSpec = RegExp | String
 */
function handleRequest(app, method, spec, handler) {
    app[method.toLowerCase()](spec, function (req, res) {
        handler(req).fork(handleError(req, res), sendResponse(req, res));
    });
    return app;
}
/**
 * Handles an error while handling the response.
 *
 * @private
 * @summary Request, ExpressResponse → Error | Response → Void
 */
function handleError(req, res) {
    return function (error) {
        console.log(error.toString());
        if (error instanceof Response)
            sendResponse(req, res)(error);
        else
            res.send(500, error);
    };
}
/**
 * Handles a response from a handler.
 *
 * @private
 * @summary Request, ExpressResponse → Response → Void
 */
function sendResponse(req, res) {
    return function (a0) {
        var r13 = Redirect.unapply(a0);
        if (r13 != null && r13.length === 1) {
            var r14 = r13[0];
            var url = r14;
            return res.redirect(url.toString());
        }
        var r15 = Render.unapply(a0);
        if (r15 != null && r15.length === 2) {
            var r16 = r15[0];
            var r17 = r15[1];
            var view = r16, options = r17;
            return res.render(view, options);
        }
        var r18 = Send.unapply(a0);
        if (r18 != null && r18.length === 3) {
            var r19 = r18[0];
            var r20 = r18[1];
            var r21 = r18[2];
            var status = r19, headers = r20, body = r21;
            return _send(res, status, headers, body);
        }
        throw new TypeError('No match');
    };
}
/**
 * Sends a response.
 *
 * @private
 * @summary ExpressResponse, Int, Object, Body → Void
 */
function _send(res, status, headers, body) {
    res.set(headers || {});
    body.cata({
        Text: function (v) {
            res.send(status, String(v));
        },
        Buffer: function (v) {
            res.send(status, v);
        },
        Value: function (v) {
            res.send(status, v);
        }
    });
}
/**
 * Returns a wrapper over Node's `Server` objects that provides
 * monadic actions.
 *
 * @private
 * @summary Server → { close: Void → Future[Error, Void] }
 */
function wrapServer(server) {
    return {
        close: function () {
            return new Future(function (reject, resolve) {
                server.close(function (error) {
                    if (error)
                        reject(error);
                    else
                        resolve();
                });
            });
        }
    };
}
// -- Public API -------------------------------------------------------
module.exports = function (express) {
    var exports = {};
    /**
   * Constructs a configuration for an Express application.
   *
   * @method
   * @summary Name → Value → Component
   */
    exports.set = curry(2, Setting);
    /**
   * Shor-hand for [`set(name, true)`](#set)
   *
   * @method
   * @summary Name → Component
   */
    exports.enable = enable;
    function enable(name) {
        return Setting(name, true);
    }
    /**
   * Shor-hand for [`set(name, false)`](#set)
   *
   * @method
   * @summary Name → Component
   */
    exports.disable = disable;
    function disable(name) {
        return Setting(name, false);
    }
    /**
   * Constructs an engine configuration for an Express application.
   *
   * @method
   * @summary String → (Path, Object, (Error, String → Void) → Void)
   */
    exports.engine = curry(2, Engine);
    /**
   * Constructs a locals configuration for an Express application.
   *
   * @method
   * @summary Object → Component
   */
    exports.locals = Locals;
    /**
   * Constructs a Plugin configuration for an Express application.
   *
   * @method
   * @summary Path → (Request, ExpressResponse, (Any → Void) → Void) → Component
   */
    exports.plugin = curry(2, Plugin);
    /**
   * Constructs a Route configuration for an Express application.
   *
   * This module also provides short-hands for HTTP verbs supported by Node.js,
   * such that you can conveniently use `VERB(spec, handler)` instead of
   * `route(VERB, spec, handler)`.
   *
   * @method
   * @summary Method → RouteSpec → (Request → Future[Error, Response]) → Component
   */
    var route = exports.route = curry(3, Route);
    /**
   * Short-hand for [`route('all')(...)`](#route).
   *
   * @method
   * @summary RouteSpec → (Request → Future[Error, Response]) → Component
   */
    exports.all = route('all');
    /**
   * Short-hand for [`route('delete')(...)`](#route).
   *
   * @method
   * @summary RouteSpec → (Request → Future[Error, Response]) → Component
   */
    exports.remove = route('delete');
    /**
   * Wraps a route handler.
   *
   * @method
   * @summary
   * ((Request → Future[Error, Response]) → (Request → Future[Error, Response]))
   * → Component
   * → Component
   */
    exports.wrap = curry(2, wrap);
    function wrap(wrapper, route$2) {
        return function (a0) {
            var r13 = Route.unapply(a0);
            if (r13 != null && r13.length === 3) {
                var r14 = r13[0];
                var r15 = r13[1];
                var r16 = r13[2];
                var method = r14, spec = r15, handler = r16;
                return Route(method, spec, wrapper(handler));
            }
            throw new TypeError('No match');
        }.call(this, route$2);
    }
    /**
   * Binds an Express server to a particular port.
   *
   * @method
   * @summary Int → App → Future[Error, Server]
   */
    exports.listen = curry(2, listen);
    function listen(port, server) {
        return new Future(function (reject, resolve) {
            var s = server.listen(port, function (error) {
                    if (error)
                        reject(error);
                    else
                        delayedResolve(this.address());
                });
            function delayedResolve(addr) {
                setTimeout(function () {
                    resolve(extend(wrapServer(s), { address: addr }));
                });
            }
        });
    }
    /**
   * Constructs an Express server from a list of configurations.
   *
   * @method
   * @summary [Component] → Server
   */
    exports.create = create;
    function create(routes) {
        return routes.reduce(configure, express());
    }
    /**
   * Constructs a JSON response.
   *
   * @method
   * @summary Object → Response
   */
    exports.json = json;
    function json(x) {
        return Send(200, { 'Content-Type': 'application/json' }, Value(x));
    }
    /**
   * Constructs a String body for a response.
   *
   * @method
   * @summary String → Content
   */
    exports.text = text;
    function text(a) {
        return Text(a);
    }
    /**
   * Constructs a JS value body for a response.
   *
   * @method
   * @summary Any → Content
   */
    exports.value = value;
    function value(a) {
        return Value(a);
    }
    /**
   * Constructs a Buffer body for a response.
   *
   * @method
   * @summary Buffer → Content
   */
    exports.buffer = buffer;
    function buffer(a) {
        return Buffer(a);
    }
    /**
   * Constructs a response for a handler.
   *
   * @method
   * @summary Int → Object → Content → Response
   */
    var send = exports.send = curry(3, Send);
    /**
   * Short-hand for [`send(200, { 'Content-Type': 'text/html' }, text(x))`](#send)
   *
   * @method
   * @summary String → Response
   */
    exports.success = compose(send(200, { 'Content-Type': 'text/html' }), text);
    /**
   * Short-hand for [`send(404, { 'Content-Type': 'text/html' }, text(x))`](#send)
   *
   * @method
   * @summary String → Response
   */
    exports.notFound = compose(send(404, { 'Content-Type': 'text/html' }), text);
    /**
   * Short-hand for [`send(500, { 'Content-Type': 'text/html' }, text(x))`](#send)
   *
   * @method
   * @summary String → Response
   */
    exports.fail = compose(send(500, { 'Content-Type': 'text/html' }), text);
    /**
   * Constructs a redirect response for a handler.
   *
   * @method
   * @summary URL → Response
   */
    exports.redirect = Redirect;
    /**
   * Constructs a render response for a handler.
   *
   * @method
   * @summary String → Object → Response
   */
    exports.render = curry(2, Render);
    // -- Exports --------------------------------------------------------
    exports.Component = Component;
    exports.Response = Response;
    exports.Content = Content;
    methods.forEach(function (m) {
        exports[m] = route(m);
    });
    return exports;
};
//# sourceMappingURL=index.js.map