all files / lib/ router.js

64.29% Statements 36/56
83.33% Branches 5/6
33.33% Functions 1/3
66.04% Lines 35/53
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                10× 10×   10×     10× 10× 10×     10×           10×     10×     10×                                 10×                                                                      
'use strict';
 
const PATH   = require('path');
const Routes = require('./routes');
const Conf   = require('./support/conf');
 
const Memory = new WeakMap();
 
class Router extends Object {}
 
module.exports = function(self){
 
    // Make sure the configuration set here won't override the one defined by the user
    let conf = Conf.call(this, self);
    Eif (!this.conf[self.name]) this.conf = conf;
    else this.conf = this.util.object(conf).merge({ [self.name]: this.conf[self.name] });
    conf = this.conf[self.name];
 
    // Create the instance and make it available via getter.
    const router = new Router();
    Memory.set(this, router);
    this.set(self.name, null, { get: getter.bind(this, self) });
 
    // define a public loader for bundles
    Object.defineProperty(router, 'load', {
        enumerable: true,
        value: Routes.loader.bind(this, self)
    });
 
    // load the routes from the router file
    const route$ = this.observable
        .of(this.path.router)
        // make sure it exists
        .switchMap(path => this.util.rx.path(path)
            .isFile()
            .map(isFile => {
                if (!isFile){
                    const base = path.replace(this.path.root + PATH.sep, '');
                    throw this.error(`${self.path}: router not found (${base})`);
                }
                return path;
            }))
        // process the router-file and return a stream of routes
        .switchMap(path => {
            const data = require(path);
            if (!this.util.is(data).object())
                throw this.error.type({name:`${self.path}:routes`, type:'Object', data});
            // define the properties
            Object.defineProperty(router, 'filename', { enumerable:true, value:path });
            Object.defineProperty(router, 'data'    , { value: data });
            Object.defineProperty(router, 'routes'  , {
                enumerable: true,
                get: Routes.getter.bind(this, self)
            });
            // Convert routes into an iterable stream
            const routes = Object
                .keys(router.data)
                .map(path => Object.assign({path}, router.data[path]));
            this.events.emit(`${self.path}:routes~before`, routes);
            return this.observable.from(routes);
        })
        // at this point each call is a route object, load it and concat them.
        .concatMap(route => router.load(route))
        // wait until all routes completed and make them accessible via getter
        .toArray()
        .do(routes => {
            this.events.emit(`${self.path}:routes`, routes);
            this.events.on('plugins:server:start~before', e => onServer.call(this, self));
        });
    return route$.mapTo(this);
}
 
// Allow the use of a dash in the function name
Object.defineProperty(module.exports, 'name', { value: 'server:router' })
 
function getter(self) {
    const router = Memory.get(this);
    this.debug(self.path, 'getter:router', router);
    return router;
}
 
// When the server is ready, register all defined routes.
function onServer(self){ this[self.name].routes
    .filter(route => route.type === 'http')
    .forEach(route => {
        // Remove non-hapi properties from route.
        const bundle = route.bundle;
        const name   = `routes:${bundle.name}`;
        delete route.bundle;
        delete route.type;
        return
        // Create a closure for each route so events can be sent automatically
        route.handler = (request, reply) => {
            const self = this.util
                .object({ bundle: { name:bundle.name, filename:bundle.path }})
                .merge(route);
            this.events.emit(`${self.path}:${name}`, self);
            this.debug(self.path, name, 'load', self);
            bundle.data.call(this, request, reply, self);
        }
        this.events.emit(`${self.path}:${name}~before`, route);
        this.debug(self.path, name, 'init');
        // register the route
        this.server.route(route);
    });
}