Socket
Socket
Sign inDemoInstall

aldo

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aldo - npm Package Compare versions

Comparing version 0.1.0-alpha.0 to 0.2.0

lib/dispatcher.js

272

lib/application.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const timers_1 = require("timers");
// import * as createDebugger from 'debug'
const TreeRouter = require("find-my-way");
const dispatcher_1 = require("./dispatcher");
const createDebugger = require("debug");
const aldo_http_1 = require("aldo-http");
const debug = createDebugger('aldo:application');
/**
* A global facade to manage routes, error handlers
*
* @class Application
* A global facade to manage routes, error handlers, dispatching, etc...
*/
class Application {
constructor() {
this._context = { app: this };
this._namedRoutes = new Map();
this._finally = _respond;
this._catchers = [];
this._posts = [];
this._pres = [];
this._tree = new TreeRouter();
this._routes = [];
this._preHandlers = [];
this._postHandlers = [];
this._dispatcher = new dispatcher_1.default(_respond);
this._context = Object.create(null);
}
/**
* Add before route middleware
* Add before route handler
*
* @param {Function} fn
* @returns {Application}
* @param fns
*/
pre(fn) {
this._pres.push(_ensureFunction(fn));
pre(...fns) {
for (let fn of fns) {
this._preHandlers.push(_ensureFunction(fn));
debug(`use pre handler: ${fn.name || '<anonymous>'}`);
}
return this;
}
/**
* Add after route middleware
* Add after route handler
*
* @param {Function} fn
* @returns {Application}
* @param fns
*/
post(fn) {
this._posts.push(_ensureFunction(fn));
post(...fns) {
for (let fn of fns) {
this._postHandlers.push(_ensureFunction(fn));
debug(`use post handler: ${fn.name || '<anonymous>'}`);
}
return this;
}
/**
* Add error middleware
* Add an error handler
*
* @param {Function} fn
* @returns {Application}
* @param fns
*/
catch(fn) {
this._catchers.push(_ensureFunction(fn));
catch(...fns) {
for (let fn of fns) {
this._dispatcher.onError(_ensureFunction(fn));
debug(`use error handler: ${fn.name || '<anonymous>'}`);
}
return this;

@@ -55,23 +57,18 @@ }

*
* @param {Function} fn
* @returns {Application}
* @param fn final request handler
*/
finally(fn) {
this._finally = _ensureFunction(fn);
this._dispatcher.onFinished(_ensureFunction(fn));
debug(`use final handler: ${fn.name || '<anonymous>'}`);
return this;
}
/**
* Add router's routes into the tree
* Register router's routes
*
* @param {Router} router
* @returns {Application}
* @param routers
*/
use(router) {
for (let route of router.routes()) {
for (let [method, fns] of route.handlers()) {
this._tree.on(method, route.path, this._compose(fns));
}
// register named route
if (route.name)
this._namedRoutes.set(route.name, route);
use(...routers) {
for (let router of routers) {
this._routes.push(...router.routes());
debug('use routes');
}

@@ -81,57 +78,64 @@ return this;

/**
* Dispatch the request/response to the matched route handler
*
* @param {Request} request
* @param {Response} response
* Return a request handler callback
*/
dispatch(request, response) {
var { method, url } = request;
var found = this._tree.find(method, url);
var ctx = this._makeContext(request, response);
// debug(`dispatch ${method} ${url}`)
// 404
if (!found) {
let err = _notFoundError(url);
this._loopError(err, ctx);
return;
}
// add url params to the context
ctx.params = found.params || {};
// invoke the route handler
found.handler(ctx);
callback() {
this._registerRoutes();
return (request, response) => {
debug(`dispatching: ${request.method} ${request.url}`);
this._dispatcher.dispatch(this.makeContext(request, response));
};
}
async start(arg, options = {}) {
var server = this._server = aldo_http_1.createServer(options, this.callback());
if (typeof arg === 'number')
arg = { port: arg };
// listen
await server.start(arg);
debug(`app started with %o`, arg);
return server;
}
/**
* Create a HTTP server and pass the arguments to `listen` method
* Stop listening for requests
*/
async stop() {
var server = this._server;
await server.stop();
debug(`app stopped`);
return server;
}
/**
* Extend the app context by adding per instance property
*
* @param {Any...} args
* @returns {Server}
* @param prop
* @param fn
*/
serve(...args) {
return aldo_http_1.createServer(this.dispatch.bind(this)).listen(...args);
bind(prop, fn) {
_ensureFunction(fn);
var field = `_${prop}`;
Reflect.defineProperty(this._context, prop, {
configurable: true,
enumerable: true,
get() {
if (this[field] === undefined) {
// private property
Reflect.defineProperty(this, field, {
value: fn(this)
});
}
return this[field];
}
});
return this;
}
/**
* Extend the app context by adding more properties
* Extend the app context by adding shared properties
*
* @param {String} prop
* @param {Any} value
* @returns {Application}
* @param prop
* @param value
*/
set(prop, value) {
var descriptor = {
configurable: true,
enumerable: true
};
// factory
if (typeof value === 'function') {
let fn = value;
let field = `_${prop}`;
descriptor.get = function _get() {
return this[field] || (this[field] = fn(this));
};
return this.bind(prop, value);
}
else {
descriptor.value = value;
}
// define
Object.defineProperty(this._context, prop, descriptor);
this._context[prop] = value;
return this;

@@ -142,4 +146,3 @@ }

*
* @param {String} prop
* @returns {Any}
* @param prop
*/

@@ -152,4 +155,3 @@ get(prop) {

*
* @param {String} prop
* @returns {Boolean}
* @param prop
*/

@@ -162,81 +164,35 @@ has(prop) {

*
* @param {Request} request
* @param {Response} response
* @returns {Object}
* @private
* @param request
* @param response
*/
_makeContext(request, response) {
makeContext(request, response) {
var ctx = Object.create(this._context);
ctx.response = response;
ctx.request = request;
ctx.params = {};
ctx.app = this;
return ctx;
}
/**
* Compose the middleware list into a callable function
* Construct the routes tree
*
* @param {Array<Function>} fns
* @returns {Function}
* @private
*/
_compose(fns) {
var handlers = [...this._pres, ...fns, ...this._posts];
return (ctx) => this._loopMiddleware(ctx, handlers);
}
/**
* Loop over the route middlewares
*
* @param {Object} ctx
* @param {Array<Function>} fns
*/
_loopMiddleware(ctx, fns) {
var i = 0;
var next = (err) => {
if (!ctx.error && err) {
this._loopError(err, ctx);
return;
_registerRoutes() {
for (let route of this._routes) {
for (let [method, fns] of route.handlers()) {
fns = [...this._preHandlers, ...fns, ...this._postHandlers];
this._dispatcher.register(method, route.path, fns);
}
var fn = fns[i++];
if (!fn) {
next = _noop;
fn = this._finally;
}
// async call
timers_1.setImmediate(fn, ctx, next);
};
next();
// TODO register named routes
}
// reset the handlers
this._routes = [];
}
/**
* Loop over the error middlewares
*
* @param {Error} err
* @param {Object} ctx
*/
_loopError(err, ctx) {
// TODO ensure err is an Error instance
// set the context error
ctx.error = err;
this._loopMiddleware(ctx, this._catchers);
}
}
exports.default = Application;
/**
* Create a 404 error instance
*
* @param {String} path
* @returns {Error}
* @private
*/
function _notFoundError(path) {
var msg = `Route not found for "${path}".`;
var error = new Error(msg);
error.code = 'NOT_FOUND';
error.expose = true;
error.status = 404;
return error;
}
/**
* Ensure the given argument is a function
*
* @param {Any} arg
* @returns {Function}
* @param arg
* @private

@@ -252,15 +208,7 @@ */

*
* @param {Object} context
* @param ctx
* @private
*/
function _respond({ response }) {
response.send();
function _respond(ctx) {
ctx.response.send();
}
/**
* Noop
*
* @private
*/
function _noop() {
// do nothing
}

@@ -5,7 +5,7 @@ "use strict";

const assert = require("assert");
const createDebugger = require("debug");
const debug = createDebugger('aldo:route');
const METHODS = ['HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
/**
* Route container
*
* @class Route
*/

@@ -16,5 +16,4 @@ class Route {

*
* @param {String} path
* @param {String} [prefix]
* @constructor
* @param path
* @param prefix
*/

@@ -29,4 +28,2 @@ constructor(path, prefix = '') {

* The route path
*
* @type {String}
*/

@@ -38,4 +35,2 @@ get path() {

* The route name
*
* @type {String}
*/

@@ -49,6 +44,6 @@ get name() {

* @param {String} name
* @returns {Route}
*/
as(name) {
this._name = name;
debug(`set route name to "${this._name}"`);
return this;

@@ -59,4 +54,3 @@ }

*
* @param {String} path
* @returns {Route}
* @param path
*

@@ -67,2 +61,3 @@ * @todo add test case for "/" path

this._prefix = _normalize(path);
debug(`set route prefix to "${this._prefix}"`);
return this;

@@ -72,4 +67,2 @@ }

* Get an iterator of the route handlers
*
* @returns {Array<[String, Array<Function>]>}
*/

@@ -82,4 +75,3 @@ handlers() {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -92,4 +84,3 @@ head(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -102,4 +93,3 @@ get(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -112,4 +102,3 @@ post(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -122,4 +111,3 @@ put(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -132,4 +120,3 @@ patch(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -142,4 +129,3 @@ delete(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -154,4 +140,3 @@ options(...fns) {

*
* @param {Function...} fns
* @returns {Route}
* @param fns
*/

@@ -166,11 +151,10 @@ all(...fns) {

*
* @param {Array<String>} methods
* @param {Function...} fns
* @returns {Route}
* @param methods
* @param fns
*/
any(methods, ...fns) {
assert(fns.length, 'At least one route handler is required.');
fns.forEach((fn) => {
for (let fn of fns) {
assert(typeof fn === 'function', 'Route handler must be a function.');
});
}
// wrap the final handler

@@ -181,2 +165,3 @@ fns = _wrapFinalHandler(fns);

assert(METHODS.includes(method.toUpperCase()), `Method '${method}' not accepted.`);
debug(`add handlers for ${method.toUpperCase()} ${this.path}`);
this._handlers.set(method, fns);

@@ -191,4 +176,3 @@ }

*
* @param {String} path
* @returns {String}
* @param path
* @private

@@ -204,6 +188,5 @@ */

/**
* Wrap the last middleware function
* Wrap the last handler
*
* @param {Array<Function>} handlers
* @returns {Array<Function>}
* @param handlers
* @private

@@ -219,23 +202,12 @@ */

*
* @param {Function} fn
* @returns {Function}
* @param fn
* @private
*/
function _wrapper(fn) {
return async (ctx, next) => {
try {
var result = await fn(ctx);
if (result instanceof Error) {
next(result);
return;
}
if (result && !ctx.response.body) {
ctx.response.body = result;
}
next();
return async (ctx) => {
var result = await fn(ctx);
if (result && !ctx.response.body) {
ctx.response.body = result;
}
catch (error) {
next(error);
}
};
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const route_1 = require("./route");
const createDebugger = require("debug");
const debug = createDebugger('aldo:router');
/**

@@ -12,3 +14,3 @@ * Routes factory and manager

this._prefix = _prefix;
this._middlewares = [];
this._handlers = [];
this._routes = [];

@@ -25,2 +27,3 @@ //

this._prefix = value;
debug(`update route prefix to ${this._prefix}`);
// set the prefix for the registered routes

@@ -47,8 +50,9 @@ for (let route of this._routes) {

route(path) {
let instance = new route_1.default(path, this._prefix);
this._routes.push(instance);
return instance;
var route = new route_1.default(path, this._prefix);
debug(`create route for ${path}`);
this._routes.push(route);
return route;
}
/**
* Use global middlewares
* Use global handlers
*

@@ -59,33 +63,98 @@ * @param {Function...} fns

use(...fns) {
fns.forEach((fn) => {
this._middlewares.push(_ensureFunction(fn));
});
for (let fn of fns) {
this._handlers.push(_ensureFunction(fn));
debug(`use route handler: ${fn.name || '<anonymous>'}`);
}
return this;
}
/**
* Make new route and set the handlers for HEAD method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
head(path, ...handlers) {
return this.route(path).head(...this._middlewares.concat(handlers));
return this.route(path).head(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for GET method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
get(path, ...handlers) {
return this.route(path).get(...this._middlewares.concat(handlers));
return this.route(path).get(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for POST method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
post(path, ...handlers) {
return this.route(path).post(...this._middlewares.concat(handlers));
return this.route(path).post(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for PUT method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
put(path, ...handlers) {
return this.route(path).put(...this._middlewares.concat(handlers));
return this.route(path).put(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for PATCH method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
patch(path, ...handlers) {
return this.route(path).patch(...this._middlewares.concat(handlers));
return this.route(path).patch(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for DELETE method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
delete(path, ...handlers) {
return this.route(path).delete(...this._middlewares.concat(handlers));
return this.route(path).delete(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for OPTIONS method
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
options(path, ...handlers) {
return this.route(path).options(...this._middlewares.concat(handlers));
return this.route(path).options(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for accepted methods
*
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
all(path, ...handlers) {
return this.route(path).all(...this._middlewares.concat(handlers));
return this.route(path).all(...this._handlers.concat(handlers));
}
/**
* Make new route and set the handlers for the given HTTP method
*
* @param {Array<String>} methods
* @param {String} path
* @param {Function...} handlers
* @returns {Route}
*/
any(methods, path, ...handlers) {
return this.route(path).any(methods, ...this._middlewares.concat(handlers));
return this.route(path).any(methods, ...this._handlers.concat(handlers));
}

@@ -92,0 +161,0 @@ }

{
"name": "aldo",
"version": "0.1.0-alpha.0",
"version": "0.2.0",
"description": "Fast web framework for Node.js",

@@ -9,4 +9,8 @@ "main": "lib/index.js",

"prepublishOnly": "tsc && npm test",
"test": "mocha -r ts-node/register \"src/**/*.spec.ts\""
"test": "mocha -R dot -r ts-node/register \"test/**/*.ts\""
},
"files": [
"lib",
"index.d.ts"
],
"repository": {

@@ -35,6 +39,8 @@ "type": "git",

"dependencies": {
"@types/debug": "0.0.30",
"@types/node": "^8.9.2",
"aldo-http": "^0.1.3",
"aldo-http": "^0.2.0",
"debug": "^3.1.0",
"find-my-way": "^1.10.1"
}
}
## Introduction
`Aldo` is another library to build web applications for Node.js.
> This project is under heavy development, and the API may change frequently.
It uses the best parts of `Koa`, `Express` and `Fastify` to make building restful applications fun and easy.
`Aldo` is yet another framework to build Node.js web applications.
It uses the best parts of `Koa` and `Express` to provide a fast engine for your web projects.
`Aldo`'s goal is to provide a secure and solid foundation for your projects, a fast execution and a fluent API.
## Installation

@@ -18,5 +16,4 @@ ```bash

```
> More tests are comming in the future
## Usage
## Hello world!
```js

@@ -37,5 +34,112 @@ // import the needed parts

// which creates and uses an internal HTTP server
app.serve(3000)
app.start(3000)
```
## To be continued...
## Request Flow
The request handling logic is similar to the `try..catch..finally` JavaScript block.
In other words, the application will try to call the route handlers one by one to the final handler.
If an error occurs, it will be handled by the error handlers before reaching the final handler which will terminate and send the response to the client.
You can use `.pre`, `.post`, `.use`, `.catch` and `.finally` application's methods to control the flow of request handling.
```js
const app = new Application()
// 1. The `try` block
// attach one or more global handlers before the route
// useful to configure global services like session, cache ...etc
app.pre(...handlers)
// use one or many routers with `.use`
app.use(...routers)
// ... etc
// attach global handlers to be executed after the route handlers
// like saving a cached version, persisting session data, setting more headers ...etc
app.post(...handlers)
// 2. The `catch` block
// attaching error handlers is done as below
app.catch(...handlers)
// 3. The `finally` block
// at last, only one final handler is used
// the default one is simply sending the response
app.finally(finalHandler)
```
> Since each method controls a processing step, the order doesn't matter any more.
> We can define routes before or after the final handler, it won't create any issue, since the routes are only compiled during the application launch.
Unlike the other web frameworks, each `handler` takes only **one** argument: the **`context`**. This object holds everything you need to handle the incoming HTTP request.
Handlers may return *void* or a promise for asynchronous operations.
So, when the current handler finished its job, the *context* object is passed automatically to the next handler, and so on, till the final handler which terminates and ends the response.
To break the chain, throw an error, to get the first error handler called instead of the next middleware.
```ts
// Handler function signature
declare type Handler = (ctx: Context) => any
```
## Context
The context object is not a proxy to the request and response properties, it's a simple plain object with only 4 mandatory properties `app`, `request`, `response` and route `params`.
Even the error handlers have the same signature, but with an additonal `error` property.
```ts
declare type Literal = { [x: string]: any }
declare interface Context extends Literal {
response: Response; // Response object provided by the package `aldo-http`
request: Request; // Request object provided by the package `aldo-http`
app: Application; // Application instance
params: Literal; // Route parameters
error?: any; // The error
}
```
To extend the request context, and add more properties, you can use `app.set(key, value)` or `app.bind(key, factory)`
```js
// set a global value to be available for all requests
app.set('mongo', dbConnection)
// set a per request property using a function to lazily get the value
// This time, each context instance has a distinct `session` property
app.bind('session', () => new Session(options))
```
`app.has(key)` and `app.get(key)` are also available to check the existence of a certain field, or to get a previously defined property.
## Router
Each `router` instance control an erea in the application, it acts like a route namespace.
You can use as many routers as you need. For example, a router to manage authentication, another for the API, a private router for admin routing, and so on.
> The order of defining routes is not important any more. Thanks to [find-my-way](https://npmjs.com/find-my-way) witch is a [radix tree](https://en.wikipedia.org/wiki/Radix_tree).
```js
const { Router } = require('aldo')
const { Users } = require('./controllers')
// Let's create an admin area router
// `/admin` is a URL prefix for all routes
const router = new Router('/admin')
// define a single handler for admin home page for the GET method
router.get('/', Users.home)
// we can define multiple handlers per HTTP method for the same route
router
.route('/Users')
.get(Users.show)
.delete(Users.delete)
.post(Users.validate, Users.add)
.put(Users.validate, Users.modify)
```
## License
[MIT License](https://opensource.org/licenses/MIT)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc