├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── benchmark ├── basic │ ├── express.ts │ ├── koa.ts │ └── noflow.ts └── index.ts ├── doc ├── changelog.md └── readme.jst.md ├── examples ├── basic.ts ├── body.ts ├── compose-middlewares.ts ├── cookie-session.ts ├── error-handling.ts ├── express-middleware.ts ├── generator.js ├── generator.ts ├── https.ts ├── proxy.ts ├── query-string.ts ├── reverse-proxy.ts ├── routes.ts ├── static.ts ├── time-log.ts └── virtual-host.ts ├── lib ├── flow.ts ├── index.ts └── utils.ts ├── nofile.js ├── package-lock.json ├── package.json ├── readme.md ├── test ├── basic.ts ├── compose.ts ├── error.ts └── testSuit │ ├── index.ts │ ├── server.crt │ └── server.key ├── tsconfig-dev.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nokit 3 | .nobone 4 | *sublime-* 5 | *.log 6 | *.js 7 | *.d.ts 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/*.db": true, 7 | "**/*.js": { 8 | "when": "$(basename).ts" 9 | }, 10 | "**/*.d.ts": true 11 | }, 12 | "typescript.tsdk": "./node_modules/typescript/lib", 13 | "editor.trimAutoWhitespace": true 14 | } -------------------------------------------------------------------------------- /benchmark/basic/express.ts: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var port = process.argv[2]; 3 | 4 | var app = express(); 5 | 6 | app.use(function (req, res, next) { 7 | next(); 8 | }); 9 | 10 | app.use(function (req, res) { 11 | res.send("hello world"); 12 | }); 13 | 14 | app.listen(port); 15 | -------------------------------------------------------------------------------- /benchmark/basic/koa.ts: -------------------------------------------------------------------------------- 1 | var koa = require("koa"); 2 | var port = process.argv[2]; 3 | 4 | var app = koa(); 5 | 6 | app.use(function * (next) { 7 | yield next; 8 | }); 9 | 10 | app.use(function * () { 11 | this.body = "hello world"; 12 | }); 13 | 14 | app.listen(port); 15 | -------------------------------------------------------------------------------- /benchmark/basic/noflow.ts: -------------------------------------------------------------------------------- 1 | var flow = require("../../lib").default; 2 | var port = process.argv[2]; 3 | 4 | var app = flow(); 5 | 6 | app.push(function ($) { 7 | return $.next(); 8 | }, function (ctx) { 9 | ctx.body("hello world"); 10 | }); 11 | 12 | app.listen(port); 13 | -------------------------------------------------------------------------------- /benchmark/index.ts: -------------------------------------------------------------------------------- 1 | var kit = require("nokit"); 2 | var port = process.argv[2]; 3 | var tName = process.argv[3]; 4 | 5 | function * request () { 6 | var count = 0; 7 | while (count++ < 3000) 8 | yield kit.request("http://127.0.0.1:" + port); 9 | } 10 | 11 | kit.sleep(1000).then(function () { 12 | console.time(tName); 13 | kit.async(3, request()).then(function () { 14 | console.timeEnd(tName); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /doc/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | - v0.7 4 | 5 | - **API CHANGE** For CommonJS require, you have to use `require("noflow").default` 6 | 7 | - v0.6.0 8 | 9 | - **API CHANGE** the `listener` api is removed 10 | - fix: the `server` bug 11 | 12 | - v0.5.6 13 | 14 | - upd: deps 15 | 16 | - v0.5.1 17 | 18 | - init 19 | -------------------------------------------------------------------------------- /doc/readme.jst.md: -------------------------------------------------------------------------------- 1 | # NoFlow 2 | 3 | A minimal server middleware composer for the future. 4 | Mostly, It is used to create http server and handle proxy. 5 | The interesting part is that it also works fine without any ES6 or ES7 syntax, 6 | it's up to you to decide how fancy it will be. And because its middlewares are just normal 7 | functions, they can be easily composed with each other. 8 | 9 | To use noflow, you only have to remember a single rule "Any async function should and will return a Promise". 10 | 11 | 12 | 13 | # Features 14 | 15 | - Super lightweight, only one dependency, 200 sloc, learn it in 5 minutes 16 | - Static-typed with typescript 17 | - Faster than Express.js and Koa, see the benchmark section 18 | - Based on Promise, works well with async/await 19 | - Supports almost all exist Express-like middlewares 20 | 21 | 22 | **For examples, goto folder [examples](examples)**. 23 | 24 | To run the examples, you have to install the dependencies of this project: `npm i`. 25 | Such as, to run the [examples/basic.js](examples/basic.js), use command like: 26 | `node_modules/.bin/noe examples/basic.js` 27 | 28 | [![NPM version](https://badge.fury.io/js/noflow.svg)](http://badge.fury.io/js/noflow) [![Build Status](https://travis-ci.org/ysmood/noflow.svg)](https://travis-ci.org/ysmood/noflow) [![Build status](https://ci.appveyor.com/api/projects/status/github/ysmood/noflow?svg=true)](https://ci.appveyor.com/project/ysmood/noflow) [![Deps Up to Date](https://david-dm.org/ysmood/noflow.svg?style=flat)](https://david-dm.org/ysmood/noflow) 29 | 30 | 31 | 32 | # Quick Start 33 | 34 | Install it: `npm i noflow`. 35 | 36 | 37 | 38 | ### Hello World Example 39 | 40 | ```javascript 41 | import flow from "noflow"; 42 | 43 | let app = flow(); 44 | 45 | // Anything pushed into the app will be converted to a 46 | // middleware object sanely, even it's a string, buffer, stream or anything else. 47 | // Here we created a server that responses only string "hello world". 48 | app.push("hello world"); 49 | 50 | app.listen(8123); 51 | ``` 52 | 53 | 54 | 55 | ### ES5 56 | 57 | Without ES7, you can still have all the goodies. 58 | 59 | ```javascript 60 | var flow = require("noflow").default; 61 | 62 | var app = flow(); 63 | 64 | app.push(function ($) { 65 | return $.next().then(function () { 66 | console.log("done"); 67 | }); 68 | }); 69 | 70 | app.push(function ($) { 71 | $.body = "hello world"; 72 | }); 73 | 74 | app.listen(8123); 75 | ``` 76 | 77 | 78 | 79 | ### ES7 80 | 81 | Designed for the future ES7. 82 | 83 | ```javascript 84 | import flow from "noflow"; 85 | 86 | let app = flow(); 87 | 88 | app.push( 89 | 90 | async ({ next }) => { 91 | await next(); 92 | console.log("done"); 93 | }, 94 | 95 | $ => $.body = "hello world" 96 | 97 | ); 98 | 99 | app.listen(8123); 100 | ``` 101 | 102 | 103 | 104 | ### Routes 105 | 106 | You can write routes quickly by using select interface of [NoKit](https://github.com/ysmood/nokit). 107 | 108 | ```javascript 109 | import flow from "noflow"; 110 | import kit from "nokit"; 111 | let { match, select } = kit.require("proxy"); 112 | 113 | let app = flow(); 114 | 115 | app.push( 116 | select("/test", $ => $.body = $.url), 117 | 118 | select( 119 | // Express.js like url selector. 120 | match("/items/:id"), 121 | $ => $.body = $.url.id 122 | ), 123 | 124 | select( 125 | { 126 | url: "/api", 127 | method: /GET|POST/ // route both GET and POST 128 | }, 129 | $ => $.body = $.method + " " + $.url 130 | ), 131 | 132 | select( 133 | { 134 | // route some special headers 135 | headers: { 136 | host: "a.com" 137 | } 138 | }, 139 | $ => $.body = "ok" 140 | ) 141 | ); 142 | 143 | app.listen(8123); 144 | ``` 145 | 146 | 147 | 148 | ### Express middleware 149 | 150 | It's easy to convert an express middleware to noflow middleware. 151 | 152 | ```javascript 153 | import flow from "noflow"; 154 | import bodyParser from "body-parser"; 155 | 156 | let app = flow(); 157 | 158 | let convert = (h) => ({ req, res, next }) => 159 | new Promise((resolve, reject) => 160 | h(req, res, (err) => { 161 | if (err) 162 | return reject(err); 163 | else 164 | return next().then(resolve); 165 | }) 166 | ); 167 | 168 | app.push( 169 | convert(bodyParser.json()), 170 | $ => { 171 | $.body = $.req.body; 172 | } 173 | ); 174 | 175 | app.listen(8123); 176 | ``` 177 | 178 | 179 | 180 | # API 181 | 182 | It's recommended to use typescript to check all the API details. 183 | 184 | <%= doc['lib/index.ts'] %> 185 | 186 | 187 | # Status code 188 | 189 | Noflow will auto handle status code `200`, `204`, `404` and `500` for you only if 190 | you haven't set the status code yourself. 191 | 192 | - `204`: If you don't set `$.body`, such as it's `null` or `undefined`. 193 | 194 | - `404`: If no middleware found. 195 | 196 | - `500`: If any middleware reject or throws an error. 197 | 198 | - `userDefined`: If user set the `$.res.statusCode` manually. 199 | 200 | - `200`: If none of the above happens. 201 | 202 | 203 | # Error handling 204 | 205 | A middleware can catch all the errors of the middlewares after it. 206 | 207 | With ES5, you can use it like normal promise error handling: 208 | 209 | ```js 210 | var flow = require("noflow").default; 211 | 212 | var app = flow(); 213 | 214 | app.push(function ($) { 215 | return $.next().catch(function (e) { 216 | $.body = e; 217 | }); 218 | }); 219 | 220 | app.push(function () { 221 | throw "error"; 222 | }); 223 | 224 | app.push(function () { 225 | // Same with the `throw "error"` 226 | return Promise.reject("error"); 227 | }); 228 | 229 | app.listen(8123); 230 | ``` 231 | 232 | With ES7, you can use try-catch directly: 233 | 234 | ```js 235 | import flow from "noflow"; 236 | 237 | let app = flow(); 238 | 239 | app.push(async ($) => { 240 | try { 241 | await $.next(); 242 | } catch (e) { 243 | $.body = e; 244 | } 245 | }); 246 | 247 | app.push(() => { 248 | throw "error"; 249 | }); 250 | 251 | app.listen(8123); 252 | ``` 253 | 254 | 255 | # [NoKit](https://github.com/ysmood/nokit) 256 | 257 | Noflow relies on the async nature of Promise, when you need async io tools, nokit will be the best choice. 258 | nokit has all the commonly used IO functions with Promise support. 259 | 260 | For example you can use them seamlessly: 261 | 262 | ```js 263 | import flow from "noflow"; 264 | import kit from "nokit"; 265 | let { select } = kit.require("proxy"); 266 | 267 | let app = flow(); 268 | 269 | app.push( 270 | select( 271 | "/a", 272 | kit.readJson("a.json") // readJson returns a Promise 273 | ), 274 | 275 | select("/b", async $ => { 276 | let txt = await kit.readFile("b.txt"); 277 | let data = await kit.request("http://test.com/" + $.url); 278 | $.body = txt + data; 279 | }) 280 | ); 281 | 282 | app.listen(8123).then(() => { 283 | kit.request('127.0.0.1:8123/a').then(kit.logs); 284 | }); 285 | ``` 286 | 287 | # Benchmark 288 | 289 | These comparisons only reflect some limited truth, no one is better than all others on all aspects. 290 | You can run it yourself in terminal: `npm run no -- benchmark`. 291 | 292 | ``` 293 | Node v5.4.0 294 | The less the better: 295 | noflow: 1839.333ms 296 | koa: 2598.171ms 297 | express: 3239.013ms 298 | ``` 299 | -------------------------------------------------------------------------------- /examples/basic.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | 3 | let app = flow(); 4 | 5 | app.push("hello world"); 6 | 7 | app.listen(8123); 8 | -------------------------------------------------------------------------------- /examples/body.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as kit from "nokit"; 3 | 4 | let app = flow(); 5 | 6 | app.push(($) => { 7 | // "$.body" can be set by any line below, flow will unbox the typed value sanely. 8 | 9 | // string 10 | $.body = "OK"; 11 | 12 | // json 13 | $.body = { a: 10 }; 14 | 15 | // stream 16 | $.body = kit.createReadStream("index.js"); 17 | 18 | // promise 19 | $.body = kit.readJson("package.json"); 20 | 21 | // buffer 22 | $.body = kit.readFileSync("nofile.js"); 23 | }); 24 | 25 | app.listen(8123); 26 | -------------------------------------------------------------------------------- /examples/compose-middlewares.ts: -------------------------------------------------------------------------------- 1 | import flow, { MiddlewareFn } from "../lib"; 2 | import { parse } from "url"; 3 | import * as kit from "nokit"; 4 | let { select } = kit.require("proxy"); 5 | 6 | let app = flow(); 7 | 8 | let parseQuery: MiddlewareFn = $ => { 9 | $["query"] = parse($.req.url, true).query; 10 | return $.next(); 11 | }; 12 | 13 | // Only the selected url will waste CPU to parse the url. 14 | app.push( 15 | select( 16 | "/item", 17 | 18 | // Here we use sub-route to compose two middlewares. 19 | flow(parseQuery, $ => $.body = $["query"].id) 20 | ) 21 | ); 22 | 23 | // Rest middlewares will keep tight and dry. 24 | app.push("OK"); 25 | 26 | app.listen(8123); 27 | -------------------------------------------------------------------------------- /examples/cookie-session.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as cookieSession from "cookie-session"; 3 | import * as kit from "nokit"; 4 | let { midToFlow } = kit.require("proxy"); 5 | 6 | let app = flow(); 7 | 8 | app.push( 9 | midToFlow( 10 | cookieSession({ 11 | name: "app", 12 | secret: "password" 13 | }) 14 | ), 15 | 16 | $ => { 17 | let session = $.req["session"]; 18 | 19 | if (session.count) { 20 | session.count++; 21 | } else { 22 | session.count = 1; 23 | } 24 | 25 | $.body = session; 26 | } 27 | ); 28 | 29 | app.listen(8123); 30 | -------------------------------------------------------------------------------- /examples/error-handling.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | 3 | let app = flow(); 4 | 5 | app.push($ => { 6 | try { 7 | return $.next(); 8 | } catch (e) { 9 | $.body = e; 10 | } 11 | }, () => { 12 | throw "error"; 13 | }); 14 | 15 | app.listen(8123); 16 | -------------------------------------------------------------------------------- /examples/express-middleware.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as bodyParser from "body-parser"; 3 | import * as kit from "nokit"; 4 | let { midToFlow } = kit.require("proxy"); 5 | 6 | let app = flow(); 7 | 8 | app.push( 9 | midToFlow(bodyParser.json()), 10 | $ => { 11 | $.body = $.req["body"]; 12 | } 13 | ); 14 | 15 | app.listen(8123); 16 | -------------------------------------------------------------------------------- /examples/generator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const lib_1 = require("../lib"); 4 | const async = require("yaku/lib/async"); 5 | const sleep = require("yaku/lib/sleep"); 6 | let app = lib_1.default(); 7 | app.push(async(function* ($) { 8 | $.body = yield sleep(1000, "OK"); 9 | })); 10 | app.listen(8123); 11 | -------------------------------------------------------------------------------- /examples/generator.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as async from "yaku/lib/async"; 3 | import * as sleep from "yaku/lib/sleep"; 4 | 5 | let app = flow(); 6 | 7 | app.push(async(function * ($) { 8 | $.body = yield sleep(1000, "OK"); 9 | })); 10 | 11 | app.listen(8123); 12 | -------------------------------------------------------------------------------- /examples/https.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This demos how to serve http and https at the same time. 3 | */ 4 | 5 | import flow from "../lib"; 6 | import * as http from "http"; 7 | import * as https from "https"; 8 | import { readFileSync } from "fs"; 9 | 10 | let opts = { 11 | key: readFileSync("test/testSuit/server.key"), 12 | cert: readFileSync("test/testSuit/server.crt") 13 | }; 14 | 15 | let routes = ["hello world"]; 16 | 17 | let handler = flow(routes); 18 | 19 | // We here use the same handler for http and https on different port. 20 | http.createServer(handler).listen(8123); 21 | https.createServer(opts, handler).listen(8124); 22 | 23 | // curl -k http://127.0.0.1:8123 24 | // curl -k https://127.0.0.1:8124 25 | -------------------------------------------------------------------------------- /examples/proxy.ts: -------------------------------------------------------------------------------- 1 | // This example is a proxy for both http, https and websocket, etc. 2 | // Set the system proxy to "127.0.0.1:8123", then have fun! 3 | 4 | import flow from "../lib"; 5 | import * as kit from "nokit"; 6 | let proxy = kit.require("proxy"); 7 | let app = flow(); 8 | 9 | // hack a js file to a local js file 10 | app.push(proxy.select(/a.js$/, ($) => $.body = kit.readFile("b.js"))); 11 | 12 | // hack a json api 13 | app.push(proxy.select(/\/item-list$/, ($) => $.body = [1, 2, 3, 4])); 14 | 15 | // hack url path starts with '/st' to local folder './static' 16 | app.push(proxy.select("/st", proxy.static("static"))); 17 | 18 | // transparent proxy all the other http requests 19 | app.push(proxy.url()); 20 | 21 | // transparent proxy https and websocket, etc 22 | app.server.on("connect", proxy.connect()); 23 | 24 | app.listen(8123); 25 | -------------------------------------------------------------------------------- /examples/query-string.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as kit from "nokit"; 3 | import { parse } from "url"; 4 | let { select } = kit.require("proxy"); 5 | 6 | let app = flow(); 7 | 8 | app.push( 9 | // Such as "/path?id=10&name=jack" 10 | select("/path", $ => { 11 | let url = parse($.req.url, true); 12 | // Here the body will be "10 - jack". 13 | $.body = `${url.query.id} - ${url.query.name}`; 14 | }) 15 | ); 16 | 17 | // Or you can create a middleware to parse query string for all followed middlewares. 18 | app.push($ => { 19 | $["query"] = parse($.req.url, true).query; 20 | return $.next(); 21 | }); 22 | 23 | // Now we can get the auto-parsed query string. 24 | app.push($ => { 25 | $.body = $["query"].id; 26 | }); 27 | 28 | app.listen(8123); 29 | -------------------------------------------------------------------------------- /examples/reverse-proxy.ts: -------------------------------------------------------------------------------- 1 | // Work like nginx virtual host. 2 | 3 | import * as kit from "nokit"; 4 | import flow from "../lib"; 5 | let { select, url, connect } = kit.require("proxy"); 6 | let app = flow(); 7 | 8 | app.push( 9 | select( 10 | { headers: { host: "a.com:8080" } }, 11 | url("127.0.0.1:8001") 12 | ) 13 | ); 14 | 15 | app.push( 16 | select( 17 | { headers: { host: "b.com:8080" } }, 18 | url("127.0.0.1:8002") 19 | ) 20 | ); 21 | 22 | // reverse proxy websocket 23 | app.server.on("upgrade", connect({ 24 | filter: (req) => req.host.match(/c\.com/), 25 | host: "127.0.0.1:8003" 26 | })); 27 | 28 | app.listen(8080); 29 | -------------------------------------------------------------------------------- /examples/routes.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as path from "path"; 3 | import * as kit from "nokit"; 4 | let { match, select } = kit.require("proxy"); 5 | 6 | let app = flow(); 7 | 8 | app.push( 9 | select("/test", $ => $.body = $.url), 10 | 11 | select( 12 | // Express.js like url selector. 13 | { url: match("/items/:id") }, 14 | $ => $.body = $.url.id 15 | ), 16 | 17 | select( 18 | { 19 | url: "/api", 20 | method: /GET|POST/ // route both GET and POST 21 | }, 22 | $ => $.body = $.method + " " + $.url 23 | ), 24 | 25 | select( 26 | // route js only 27 | { url: url => path.extname(url) === ".js" ? "js" : null }, 28 | $ => $.body = $.url 29 | ), 30 | 31 | select( 32 | { 33 | // route some special headers 34 | headers: { 35 | host: "a.com" 36 | } 37 | }, 38 | $ => $.body = "ok" 39 | ) 40 | ); 41 | 42 | app.listen(8123); 43 | -------------------------------------------------------------------------------- /examples/static.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | import * as kit from "nokit"; 3 | let { select, static: st } = kit.require("proxy"); 4 | 5 | let app = flow(); 6 | 7 | app.push( 8 | // when you visit `/st/a.js` will return local file 'static/a.js' 9 | select("/st", st("static")) 10 | ); 11 | 12 | app.listen(8123); 13 | -------------------------------------------------------------------------------- /examples/time-log.ts: -------------------------------------------------------------------------------- 1 | import flow from "../lib"; 2 | let app = flow(); 3 | 4 | let timeSpan = () => { 5 | let start = Date.now(); 6 | return () => Date.now() - start; 7 | }; 8 | 9 | // x-response-time 10 | app.push(({ res, next }) => { 11 | let ts = timeSpan(); 12 | return next().then(() => { 13 | res.setHeader("x-response-time", ts() + "ms"); 14 | }); 15 | }); 16 | 17 | // logger 18 | app.push(({ req: { method, url }, next }) => { 19 | let ts = timeSpan(); 20 | return next().then(() => { 21 | console.log("%s %s - %s", method, url, ts()); 22 | }); 23 | }); 24 | 25 | // response 26 | app.push($ => { 27 | $.body = "hello world"; 28 | }); 29 | 30 | app.listen(8123); 31 | -------------------------------------------------------------------------------- /examples/virtual-host.ts: -------------------------------------------------------------------------------- 1 | // Work like nginx virtual host. 2 | 3 | let kit = require("nokit"); 4 | import flow from "../lib"; 5 | let { select, static: st } = kit.require("proxy"); 6 | let app = flow(); 7 | 8 | app.push( 9 | select( 10 | { headers: { host: "a.com:8080" } }, 11 | st("/var/www/a") 12 | ) 13 | ); 14 | 15 | app.push( 16 | select( 17 | { headers: { host: "b.com:8080" } }, 18 | st("/var/www/b") 19 | ) 20 | ); 21 | 22 | app.listen(8080); 23 | -------------------------------------------------------------------------------- /lib/flow.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | For the sake of performance don't use `let` key word here. 5 | */ 6 | 7 | import { isFunction, isArray } from "./utils"; 8 | import * as http from "http"; 9 | import { Stream } from "stream"; 10 | import Promise, { Thenable } from "yaku"; 11 | 12 | let { STATUS_CODES } = http; 13 | 14 | 15 | export interface Context { 16 | /** 17 | * It will be auto set as the response body. 18 | */ 19 | body: String | Buffer | Stream | Thenable | Object; 20 | 21 | /** 22 | * An IncomingMessage object is created by http.Server or http. 23 | * ClientRequest and passed as the first argument to the 'request' and 'response' event respectively. 24 | * It may be used to access response status, headers and data. 25 | * https://nodejs.org/api/http.html#http_http_incomingmessage 26 | */ 27 | req: http.IncomingMessage; 28 | 29 | /** 30 | * This object is created internally by a HTTP server--not by the user. It is passed as the second parameter to the 'request' event. 31 | * The response implements the Writable Stream interface. 32 | * https://nodejs.org/api/http.html#http_class_http_serverresponse 33 | */ 34 | res: http.ServerResponse; 35 | 36 | /** 37 | * It returns a promise which settles after all the next middlewares are setttled. 38 | */ 39 | next: () => Promise; 40 | 41 | } 42 | 43 | export interface MiddlewareFn { 44 | ($: Context): Thenable | any; 45 | } 46 | 47 | export type Middleware = MiddlewareFn | Object; 48 | 49 | export interface FlowHandler extends MiddlewareFn { 50 | (req: http.IncomingMessage, res: http.ServerResponse): Promise; 51 | } 52 | 53 | 54 | /** 55 | * A promise based function composer. 56 | * @example 57 | * Noflow encourages composition. 58 | * ```js 59 | * import flow from "noflow" 60 | * let app = flow(); 61 | * let c = 0; 62 | * app.push( 63 | * $ => $.next(c++), 64 | * flow( 65 | * $ => $.next(c++), 66 | * flow( 67 | * $ => $.next(c++), 68 | * $ => $.next(c++) 69 | * ) 70 | * ), 71 | * $ => $.body = c 72 | * ); 73 | * app.listen(8123); 74 | * ``` 75 | */ 76 | let flow = function (middlewares: Middleware[]): FlowHandler { 77 | return function (req, res?) { 78 | let $: Context, parentNext, next; 79 | 80 | // If it comes from a http listener, else it comes from a sub noflow. 81 | if (res) { 82 | $ = { req: req, res: res, body: null, next: null }; 83 | } else { 84 | $ = req; 85 | parentNext = $.next; 86 | 87 | req = $.req; 88 | res = $.res; 89 | } 90 | 91 | let index = 0; 92 | 93 | // Wrap the next middleware. 94 | next = $.next = function () { 95 | let mid = middlewares[index++]; 96 | if (mid === undefined) { 97 | // TODO: #4 98 | if (parentNext) { 99 | return parentNext(); 100 | } else { 101 | return Promise.resolve(error404($)); 102 | } 103 | } 104 | 105 | let ret = tryMid(ensureMid(mid), $); 106 | 107 | // Check if the fn has thrown error. 108 | if (ret === tryMid) { 109 | return Promise.reject(tryMidErr); 110 | } else { 111 | return Promise.resolve(ret); 112 | } 113 | }; 114 | 115 | // Begin the initial middleware. 116 | let promise = next(); 117 | 118 | // The root middleware will finnally end the entire $ peacefully. 119 | if (!parentNext) { 120 | return promise 121 | .then(function () { return endCtx($); }) 122 | .then(undefined, function (err) { return errorAndEndCtx(err, $); }); 123 | } 124 | 125 | return promise; 126 | }; 127 | }; 128 | 129 | // Convert anything to a middleware function. 130 | function ensureMid(mid: Middleware) { 131 | if (isFunction(mid)) return mid; 132 | 133 | return function ($: Context) { $.body = mid; }; 134 | } 135 | 136 | // for better performance, hack v8. 137 | let tryMidErr; 138 | function tryMid(fn: MiddlewareFn, $: Context): Thenable | typeof tryMid { 139 | try { 140 | return fn($); 141 | } catch (err) { 142 | tryMidErr = err; 143 | return tryMid; 144 | } 145 | } 146 | 147 | function endRes($: Context, data: string | Buffer, isStr?: boolean) { 148 | let buf; 149 | 150 | if (isStr) { 151 | buf = new Buffer(data); 152 | } else { 153 | buf = data; 154 | } 155 | 156 | if (!$.res.headersSent) { 157 | $.res.setHeader("Content-Length", buf.length); 158 | } 159 | 160 | $.res.end(buf); 161 | } 162 | 163 | function setStatusCode(res: http.ServerResponse, code: number) { 164 | if (res.statusCode === 200) res.statusCode = code; 165 | } 166 | 167 | function endEmpty(res: http.ServerResponse) { 168 | setStatusCode(res, 204); 169 | res.end(); 170 | } 171 | 172 | function endCtx($: Context) { 173 | let body = $.body; 174 | let res = $.res; 175 | 176 | switch (typeof body) { 177 | case "string": 178 | // String 179 | setContentType(res, "text/html; charset=utf-8"); 180 | endRes($, body, true); 181 | break; 182 | 183 | case "object": 184 | // Null 185 | if (body == null) { 186 | endEmpty(res); 187 | 188 | // Stream 189 | } else if (body instanceof Stream) { 190 | setContentType(res, "application/octet-stream"); 191 | body.pipe(res); 192 | 193 | // Buffer 194 | } else if (body instanceof Buffer) { 195 | setContentType(res, "application/octet-stream"); 196 | endRes($, body); 197 | 198 | // Promise 199 | } else if (isFunction((>body).then)) { 200 | return (>body).then(function (data) { 201 | $.body = data; 202 | return endCtx($); 203 | }); 204 | 205 | // Raw Object 206 | } else { 207 | setContentType(res, "application/json; charset=utf-8"); 208 | endRes($, JSON.stringify(body), true); 209 | } 210 | break; 211 | 212 | case "undefined": 213 | // Undefined 214 | endEmpty(res); 215 | break; 216 | 217 | default: 218 | // Other situations, such as Number 219 | setContentType(res, "text/plain"); 220 | endRes($, body + "", true); 221 | break; 222 | } 223 | } 224 | 225 | function setContentType(res: http.ServerResponse, type: string) { 226 | if (!res.headersSent && !res.getHeader("content-type")) { 227 | res.setHeader("Content-Type", type); 228 | } 229 | } 230 | 231 | function errorAndEndCtx(err: Error | any, $: Context) { 232 | setStatusCode($.res, 500); 233 | 234 | if (process.env.NODE_ENV === "production") { 235 | $.body = STATUS_CODES[$.res.statusCode]; 236 | } else { 237 | // print the error details 238 | if (err instanceof Error) 239 | $.body = err.stack; 240 | else 241 | $.body = err + ""; 242 | } 243 | 244 | // end the context 245 | return endCtx($); 246 | } 247 | 248 | function error404($: Context) { 249 | setStatusCode($.res, 404); 250 | $.body = STATUS_CODES[$.res.statusCode]; 251 | } 252 | 253 | export default function (middlewares: Middleware[]) { 254 | // Make sure we pass in an array 255 | if (!isArray(middlewares)) { 256 | middlewares = [].slice.call(arguments); 257 | } 258 | 259 | return flow(middlewares); 260 | }; 261 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import _flow, { Middleware, MiddlewareFn, FlowHandler } from "./flow"; 4 | import * as http from "http"; 5 | import promisify = require("yaku/lib/promisify"); 6 | import Promise from "yaku"; 7 | 8 | export interface RoutesListen { 9 | (): Promise; 10 | 11 | (port: number, hostname?: string, backlog?: number): Promise; 12 | (port: number, hostname?: string): Promise; 13 | (path: string): Promise; 14 | (handle: any): Promise; 15 | } 16 | 17 | export interface RoutesClose { 18 | (): Promise; 19 | } 20 | 21 | export class Routes extends Array { 22 | 23 | constructor () { 24 | super(); 25 | 26 | this.server = http.createServer(_flow(this)); 27 | this.listen = promisify(this.server.listen, this.server); 28 | this.address = this.server.address.bind(this.server); 29 | this.close = promisify(this.server.close, this.server); 30 | } 31 | 32 | server: http.Server; 33 | 34 | address: () => { port: number; family: string; address: string; }; 35 | 36 | listen: RoutesListen; 37 | 38 | close: RoutesClose; 39 | 40 | } 41 | 42 | export type Middleware = Middleware; 43 | export interface MiddlewareFn extends MiddlewareFn {}; 44 | 45 | export interface Flow { 46 | (): Routes; 47 | (...middlewares: Middleware[]): FlowHandler; 48 | } 49 | 50 | /** 51 | * Create an array instance with some handy server helper methods. 52 | * @param FlowArray middlewares Optional. If not provided, the return type will be 53 | * a FlowArray, else a middleware Function. 54 | * @return FlowArray | Function 55 | * @example 56 | * ```js 57 | * import flow from "noflow" 58 | * let app = flow(); 59 | * app.push("OK"); 60 | * app.listen(8123).then(() => console.log("started")); 61 | * ``` 62 | */ 63 | let flow: Flow = function () { 64 | if (arguments.length > 0) { 65 | return _flow.apply(0, arguments); 66 | } 67 | 68 | let routes = new Routes(); 69 | 70 | return routes; 71 | }; 72 | 73 | export default flow; 74 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export function isFunction (value): boolean { 4 | return typeof value === "function"; 5 | } 6 | 7 | export function isArray (value): boolean { 8 | return value instanceof Array; 9 | } 10 | -------------------------------------------------------------------------------- /nofile.js: -------------------------------------------------------------------------------- 1 | var kit = require("nokit") 2 | 3 | module.exports = (task, option) => { 4 | 5 | option("-n, --name ", "example file name", "examples/basic"); 6 | option("-g, --grep <.*>", "unit test regex filter", ".*"); 7 | 8 | task("default", "run an example", kit.async(function * (opts) { 9 | yield kit.spawn('tsc') 10 | 11 | kit.spawn('tsc', ['-w', '-p', 'tsconfig-dev.json']) 12 | 13 | if (!opts.name) return 14 | 15 | kit.monitorApp({ 16 | bin: "node", 17 | args: [opts.name], 18 | isNodeDeps: false, 19 | watchList: ["examples/**/*.js", "src/**/*.js"] 20 | }); 21 | })); 22 | 23 | task("build", ["lint", "build-ts"], "build", () => { 24 | return kit.warp("lib/*.ts") 25 | .load( 26 | kit.drives.comment2md({ h: 2, tpl: "doc/readme.jst.md" }) 27 | ).run(); 28 | }); 29 | 30 | task("build-ts", "build src to lib", () => { 31 | return kit.spawn("tsc"); 32 | }); 33 | 34 | task("watch-test", "run & watch test api", (opts) => 35 | kit.spawn("junit", [ 36 | "-g", opts.grep, 37 | "-w", "{src,test}/**/*.js", 38 | "test/*.js" 39 | ]) 40 | ); 41 | 42 | task("lint", "lint all code of this project", () => { 43 | return kit.glob("{examples,lib,test}/**/*.ts").then((paths) => { 44 | return kit.spawn("tslint", ["-c", "tslint.json", ...paths]) 45 | }); 46 | }); 47 | 48 | task("test", ["lint"], "run test once", (opts) => 49 | kit.spawn("junit", [ 50 | "-t", 20000, 51 | "-g", opts.grep, 52 | "test/*.js" 53 | ]) 54 | ); 55 | 56 | task("benchmark", "run benchmark", () => { 57 | process.env.NODE_ENV = "production"; 58 | let paths = kit.globSync("benchmark/basic/*.js"); 59 | let port = 3120; 60 | console.log(`Node ${process.version}`); 61 | console.log(`The less the better:`); 62 | return kit.flow(paths.reverse().map((path) => { 63 | return () => { 64 | let p = port++; 65 | let name = kit.path.basename(path, ".js"); 66 | let child = kit.spawn("node", [path, p]).process; 67 | 68 | return kit.spawn("node", ["benchmark/index.js", p, name]) 69 | .then(() => { 70 | child.kill(); 71 | }); 72 | }; 73 | }))(); 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noflow", 3 | "version": "0.8.10", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.0.16", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.16.tgz", 10 | "integrity": "sha512-P2XfbkmcAnP/XT5J5m59cQPbcIbszCwXRdngnBZefmqt1RgOv4RIFoIkG85QFDHWIt1T6bXogZP/tvh2dm/xEQ==", 11 | "dev": true 12 | }, 13 | "accepts": { 14 | "version": "1.3.3", 15 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 16 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", 17 | "dev": true, 18 | "requires": { 19 | "mime-types": "2.1.16", 20 | "negotiator": "0.6.1" 21 | } 22 | }, 23 | "ansi-regex": { 24 | "version": "2.1.1", 25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 26 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 27 | "dev": true 28 | }, 29 | "any-promise": { 30 | "version": "1.3.0", 31 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 32 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", 33 | "dev": true 34 | }, 35 | "array-flatten": { 36 | "version": "1.1.1", 37 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 38 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 39 | "dev": true 40 | }, 41 | "babel-code-frame": { 42 | "version": "6.22.0", 43 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", 44 | "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", 45 | "dev": true, 46 | "requires": { 47 | "chalk": "1.1.3", 48 | "esutils": "2.0.2", 49 | "js-tokens": "3.0.2" 50 | }, 51 | "dependencies": { 52 | "ansi-styles": { 53 | "version": "2.2.1", 54 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 55 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 56 | "dev": true 57 | }, 58 | "chalk": { 59 | "version": "1.1.3", 60 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 61 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 62 | "dev": true, 63 | "requires": { 64 | "ansi-styles": "2.2.1", 65 | "escape-string-regexp": "1.0.5", 66 | "has-ansi": "2.0.0", 67 | "strip-ansi": "3.0.1", 68 | "supports-color": "2.0.0" 69 | } 70 | }, 71 | "supports-color": { 72 | "version": "2.0.0", 73 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 74 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 75 | "dev": true 76 | } 77 | } 78 | }, 79 | "balanced-match": { 80 | "version": "1.0.0", 81 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 82 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 83 | "dev": true 84 | }, 85 | "body-parser": { 86 | "version": "1.17.2", 87 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 88 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", 89 | "dev": true, 90 | "requires": { 91 | "bytes": "2.4.0", 92 | "content-type": "1.0.2", 93 | "debug": "2.6.7", 94 | "depd": "1.1.0", 95 | "http-errors": "1.6.1", 96 | "iconv-lite": "0.4.15", 97 | "on-finished": "2.3.0", 98 | "qs": "6.4.0", 99 | "raw-body": "2.2.0", 100 | "type-is": "1.6.15" 101 | } 102 | }, 103 | "brace-expansion": { 104 | "version": "1.1.8", 105 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 106 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 107 | "dev": true, 108 | "requires": { 109 | "balanced-match": "1.0.0", 110 | "concat-map": "0.0.1" 111 | } 112 | }, 113 | "bytes": { 114 | "version": "2.4.0", 115 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 116 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", 117 | "dev": true 118 | }, 119 | "co": { 120 | "version": "4.6.0", 121 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 122 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 123 | "dev": true 124 | }, 125 | "colors": { 126 | "version": "1.1.2", 127 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", 128 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", 129 | "dev": true 130 | }, 131 | "commander": { 132 | "version": "2.9.0", 133 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 134 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 135 | "dev": true, 136 | "requires": { 137 | "graceful-readlink": "1.0.1" 138 | } 139 | }, 140 | "concat-map": { 141 | "version": "0.0.1", 142 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 143 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 144 | "dev": true 145 | }, 146 | "content-disposition": { 147 | "version": "0.5.2", 148 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 149 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", 150 | "dev": true 151 | }, 152 | "content-type": { 153 | "version": "1.0.2", 154 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 155 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", 156 | "dev": true 157 | }, 158 | "cookie": { 159 | "version": "0.3.1", 160 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 161 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", 162 | "dev": true 163 | }, 164 | "cookie-session": { 165 | "version": "2.0.0-beta.2", 166 | "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0-beta.2.tgz", 167 | "integrity": "sha1-kbIBID/y7biXv9snGdraeEpI+xs=", 168 | "dev": true, 169 | "requires": { 170 | "cookies": "0.7.0", 171 | "debug": "2.6.8", 172 | "on-headers": "1.0.1", 173 | "safe-buffer": "5.0.1" 174 | }, 175 | "dependencies": { 176 | "debug": { 177 | "version": "2.6.8", 178 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 179 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 180 | "dev": true, 181 | "requires": { 182 | "ms": "2.0.0" 183 | } 184 | } 185 | } 186 | }, 187 | "cookie-signature": { 188 | "version": "1.0.6", 189 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 190 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 191 | "dev": true 192 | }, 193 | "cookies": { 194 | "version": "0.7.0", 195 | "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.0.tgz", 196 | "integrity": "sha1-C8lh2RDDUlSYD8fJ7/XaEgEbvwA=", 197 | "dev": true, 198 | "requires": { 199 | "depd": "1.1.0", 200 | "keygrip": "1.0.1" 201 | } 202 | }, 203 | "debug": { 204 | "version": "2.6.7", 205 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 206 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", 207 | "dev": true, 208 | "requires": { 209 | "ms": "2.0.0" 210 | } 211 | }, 212 | "deep-equal": { 213 | "version": "1.0.1", 214 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 215 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 216 | "dev": true 217 | }, 218 | "delegates": { 219 | "version": "1.0.0", 220 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 221 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", 222 | "dev": true 223 | }, 224 | "depd": { 225 | "version": "1.1.0", 226 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 227 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", 228 | "dev": true 229 | }, 230 | "destroy": { 231 | "version": "1.0.4", 232 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 233 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 234 | "dev": true 235 | }, 236 | "diff": { 237 | "version": "3.3.0", 238 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", 239 | "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", 240 | "dev": true 241 | }, 242 | "ee-first": { 243 | "version": "1.1.1", 244 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 245 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 246 | "dev": true 247 | }, 248 | "encodeurl": { 249 | "version": "1.0.1", 250 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 251 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", 252 | "dev": true 253 | }, 254 | "error-inject": { 255 | "version": "1.0.0", 256 | "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", 257 | "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", 258 | "dev": true 259 | }, 260 | "escape-html": { 261 | "version": "1.0.3", 262 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 263 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 264 | "dev": true 265 | }, 266 | "escape-string-regexp": { 267 | "version": "1.0.5", 268 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 269 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 270 | "dev": true 271 | }, 272 | "esutils": { 273 | "version": "2.0.2", 274 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 275 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 276 | "dev": true 277 | }, 278 | "etag": { 279 | "version": "1.8.0", 280 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 281 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=", 282 | "dev": true 283 | }, 284 | "express": { 285 | "version": "4.15.3", 286 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 287 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=", 288 | "dev": true, 289 | "requires": { 290 | "accepts": "1.3.3", 291 | "array-flatten": "1.1.1", 292 | "content-disposition": "0.5.2", 293 | "content-type": "1.0.2", 294 | "cookie": "0.3.1", 295 | "cookie-signature": "1.0.6", 296 | "debug": "2.6.7", 297 | "depd": "1.1.0", 298 | "encodeurl": "1.0.1", 299 | "escape-html": "1.0.3", 300 | "etag": "1.8.0", 301 | "finalhandler": "1.0.3", 302 | "fresh": "0.5.0", 303 | "merge-descriptors": "1.0.1", 304 | "methods": "1.1.2", 305 | "on-finished": "2.3.0", 306 | "parseurl": "1.3.1", 307 | "path-to-regexp": "0.1.7", 308 | "proxy-addr": "1.1.5", 309 | "qs": "6.4.0", 310 | "range-parser": "1.2.0", 311 | "send": "0.15.3", 312 | "serve-static": "1.12.3", 313 | "setprototypeof": "1.0.3", 314 | "statuses": "1.3.1", 315 | "type-is": "1.6.15", 316 | "utils-merge": "1.0.0", 317 | "vary": "1.1.1" 318 | }, 319 | "dependencies": { 320 | "path-to-regexp": { 321 | "version": "0.1.7", 322 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 323 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 324 | "dev": true 325 | } 326 | } 327 | }, 328 | "finalhandler": { 329 | "version": "1.0.3", 330 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 331 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=", 332 | "dev": true, 333 | "requires": { 334 | "debug": "2.6.7", 335 | "encodeurl": "1.0.1", 336 | "escape-html": "1.0.3", 337 | "on-finished": "2.3.0", 338 | "parseurl": "1.3.1", 339 | "statuses": "1.3.1", 340 | "unpipe": "1.0.0" 341 | } 342 | }, 343 | "forwarded": { 344 | "version": "0.1.0", 345 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 346 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=", 347 | "dev": true 348 | }, 349 | "fresh": { 350 | "version": "0.5.0", 351 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 352 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=", 353 | "dev": true 354 | }, 355 | "fs.realpath": { 356 | "version": "1.0.0", 357 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 358 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 359 | "dev": true 360 | }, 361 | "glob": { 362 | "version": "7.1.2", 363 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 364 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 365 | "dev": true, 366 | "requires": { 367 | "fs.realpath": "1.0.0", 368 | "inflight": "1.0.6", 369 | "inherits": "2.0.3", 370 | "minimatch": "3.0.4", 371 | "once": "1.4.0", 372 | "path-is-absolute": "1.0.1" 373 | }, 374 | "dependencies": { 375 | "minimatch": { 376 | "version": "3.0.4", 377 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 378 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 379 | "dev": true, 380 | "requires": { 381 | "brace-expansion": "1.1.8" 382 | } 383 | } 384 | } 385 | }, 386 | "graceful-readlink": { 387 | "version": "1.0.1", 388 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 389 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 390 | "dev": true 391 | }, 392 | "has-ansi": { 393 | "version": "2.0.0", 394 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 395 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 396 | "dev": true, 397 | "requires": { 398 | "ansi-regex": "2.1.1" 399 | } 400 | }, 401 | "http-assert": { 402 | "version": "1.3.0", 403 | "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.3.0.tgz", 404 | "integrity": "sha1-oxpc+IyHPsu1eWkH1NbxMujAHko=", 405 | "dev": true, 406 | "requires": { 407 | "deep-equal": "1.0.1", 408 | "http-errors": "1.6.1" 409 | } 410 | }, 411 | "http-errors": { 412 | "version": "1.6.1", 413 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 414 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", 415 | "dev": true, 416 | "requires": { 417 | "depd": "1.1.0", 418 | "inherits": "2.0.3", 419 | "setprototypeof": "1.0.3", 420 | "statuses": "1.3.1" 421 | } 422 | }, 423 | "iconv-lite": { 424 | "version": "0.4.15", 425 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 426 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", 427 | "dev": true 428 | }, 429 | "inflight": { 430 | "version": "1.0.6", 431 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 432 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 433 | "dev": true, 434 | "requires": { 435 | "once": "1.4.0", 436 | "wrappy": "1.0.2" 437 | } 438 | }, 439 | "inherits": { 440 | "version": "2.0.3", 441 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 442 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 443 | "dev": true 444 | }, 445 | "ipaddr.js": { 446 | "version": "1.4.0", 447 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 448 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=", 449 | "dev": true 450 | }, 451 | "is-generator-function": { 452 | "version": "1.0.6", 453 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.6.tgz", 454 | "integrity": "sha1-nnFlPNFf/zQcecQVFGChMdMen8Q=", 455 | "dev": true 456 | }, 457 | "isarray": { 458 | "version": "0.0.1", 459 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 460 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 461 | "dev": true 462 | }, 463 | "jhash": { 464 | "version": "0.1.10", 465 | "resolved": "https://registry.npmjs.org/jhash/-/jhash-0.1.10.tgz", 466 | "integrity": "sha1-zhtYwL9vR0V/9iTC4CjQuJ1ih1c=", 467 | "dev": true 468 | }, 469 | "js-tokens": { 470 | "version": "3.0.2", 471 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 472 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 473 | "dev": true 474 | }, 475 | "junit": { 476 | "version": "1.4.9", 477 | "resolved": "https://registry.npmjs.org/junit/-/junit-1.4.9.tgz", 478 | "integrity": "sha1-a8EtKFNtvwKEwz28915C2yc/PaQ=", 479 | "dev": true, 480 | "requires": { 481 | "commander": "2.9.0", 482 | "nofs": "0.11.20", 483 | "yaku": "0.17.8" 484 | }, 485 | "dependencies": { 486 | "yaku": { 487 | "version": "0.17.8", 488 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.17.8.tgz", 489 | "integrity": "sha1-Yf7cQIAsadD4IzJGkUHnBNFNh2o=", 490 | "dev": true 491 | } 492 | } 493 | }, 494 | "keygrip": { 495 | "version": "1.0.1", 496 | "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.1.tgz", 497 | "integrity": "sha1-sC+kgW7vIajEs1yp5Skh/8iaMOk=", 498 | "dev": true 499 | }, 500 | "koa": { 501 | "version": "2.3.0", 502 | "resolved": "https://registry.npmjs.org/koa/-/koa-2.3.0.tgz", 503 | "integrity": "sha1-nh6OTaQBg5xXuFJ+rcV/dhJ1Vac=", 504 | "dev": true, 505 | "requires": { 506 | "accepts": "1.3.3", 507 | "content-disposition": "0.5.2", 508 | "content-type": "1.0.2", 509 | "cookies": "0.7.0", 510 | "debug": "2.6.7", 511 | "delegates": "1.0.0", 512 | "depd": "1.1.0", 513 | "destroy": "1.0.4", 514 | "error-inject": "1.0.0", 515 | "escape-html": "1.0.3", 516 | "fresh": "0.5.0", 517 | "http-assert": "1.3.0", 518 | "http-errors": "1.6.1", 519 | "is-generator-function": "1.0.6", 520 | "koa-compose": "4.0.0", 521 | "koa-convert": "1.2.0", 522 | "koa-is-json": "1.0.0", 523 | "mime-types": "2.1.16", 524 | "on-finished": "2.3.0", 525 | "only": "0.0.2", 526 | "parseurl": "1.3.1", 527 | "statuses": "1.3.1", 528 | "type-is": "1.6.15", 529 | "vary": "1.1.1" 530 | } 531 | }, 532 | "koa-compose": { 533 | "version": "4.0.0", 534 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.0.0.tgz", 535 | "integrity": "sha1-KAClE9nDYe8NY4UrA45Pby1adzw=", 536 | "dev": true 537 | }, 538 | "koa-convert": { 539 | "version": "1.2.0", 540 | "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", 541 | "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", 542 | "dev": true, 543 | "requires": { 544 | "co": "4.6.0", 545 | "koa-compose": "3.2.1" 546 | }, 547 | "dependencies": { 548 | "koa-compose": { 549 | "version": "3.2.1", 550 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", 551 | "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", 552 | "dev": true, 553 | "requires": { 554 | "any-promise": "1.3.0" 555 | } 556 | } 557 | } 558 | }, 559 | "koa-is-json": { 560 | "version": "1.0.0", 561 | "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", 562 | "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", 563 | "dev": true 564 | }, 565 | "media-typer": { 566 | "version": "0.3.0", 567 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 568 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 569 | "dev": true 570 | }, 571 | "merge-descriptors": { 572 | "version": "1.0.1", 573 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 574 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 575 | "dev": true 576 | }, 577 | "methods": { 578 | "version": "1.1.2", 579 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 580 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 581 | "dev": true 582 | }, 583 | "mime": { 584 | "version": "1.3.4", 585 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 586 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", 587 | "dev": true 588 | }, 589 | "mime-db": { 590 | "version": "1.29.0", 591 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", 592 | "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", 593 | "dev": true 594 | }, 595 | "mime-types": { 596 | "version": "2.1.16", 597 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", 598 | "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", 599 | "dev": true, 600 | "requires": { 601 | "mime-db": "1.29.0" 602 | } 603 | }, 604 | "minimatch": { 605 | "version": "3.0.3", 606 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", 607 | "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", 608 | "dev": true, 609 | "requires": { 610 | "brace-expansion": "1.1.8" 611 | } 612 | }, 613 | "ms": { 614 | "version": "2.0.0", 615 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 616 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 617 | "dev": true 618 | }, 619 | "negotiator": { 620 | "version": "0.6.1", 621 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 622 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", 623 | "dev": true 624 | }, 625 | "noflow": { 626 | "version": "0.8.10", 627 | "resolved": "https://registry.npmjs.org/noflow/-/noflow-0.8.10.tgz", 628 | "integrity": "sha1-n6RWEHintwuBvB54aZn9EOFIT1Y=", 629 | "dev": true, 630 | "requires": { 631 | "yaku": "0.17.9" 632 | }, 633 | "dependencies": { 634 | "yaku": { 635 | "version": "0.17.9", 636 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.17.9.tgz", 637 | "integrity": "sha1-Thxs6DIawRWFT/mVU+T3IOivVic=", 638 | "dev": true 639 | } 640 | } 641 | }, 642 | "nofs": { 643 | "version": "0.11.20", 644 | "resolved": "https://registry.npmjs.org/nofs/-/nofs-0.11.20.tgz", 645 | "integrity": "sha1-F+avLNo8eT6GIdkDx61/EXmU7Y4=", 646 | "dev": true, 647 | "requires": { 648 | "minimatch": "3.0.3", 649 | "yaku": "0.17.7" 650 | }, 651 | "dependencies": { 652 | "yaku": { 653 | "version": "0.17.7", 654 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.17.7.tgz", 655 | "integrity": "sha1-2jHY6+e8ERI+No2QpZizsBfMm00=", 656 | "dev": true 657 | } 658 | } 659 | }, 660 | "nokit": { 661 | "version": "0.24.5", 662 | "resolved": "https://registry.npmjs.org/nokit/-/nokit-0.24.5.tgz", 663 | "integrity": "sha512-yTZv7iyQTvHk+wLgmlwrXprASjVqxrW6jQy5AV2ay8GZNlruvdv84tDFBPePtLcf8OL2grY7CNr23IwToN+wWg==", 664 | "dev": true, 665 | "requires": { 666 | "jhash": "0.1.10", 667 | "noflow": "0.8.10", 668 | "nofs": "0.11.21", 669 | "semver": "5.3.0", 670 | "yaku": "0.18.0" 671 | }, 672 | "dependencies": { 673 | "nofs": { 674 | "version": "0.11.21", 675 | "resolved": "https://registry.npmjs.org/nofs/-/nofs-0.11.21.tgz", 676 | "integrity": "sha1-fKfVJGV3XEfn+UuuutjlG2FjeHo=", 677 | "dev": true, 678 | "requires": { 679 | "minimatch": "3.0.3", 680 | "yaku": "0.17.9" 681 | }, 682 | "dependencies": { 683 | "yaku": { 684 | "version": "0.17.9", 685 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.17.9.tgz", 686 | "integrity": "sha1-Thxs6DIawRWFT/mVU+T3IOivVic=", 687 | "dev": true 688 | } 689 | } 690 | }, 691 | "yaku": { 692 | "version": "0.18.0", 693 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.18.0.tgz", 694 | "integrity": "sha1-+37mgtC0lhn2oUhnCg/Js4S1aLs=", 695 | "dev": true 696 | } 697 | } 698 | }, 699 | "on-finished": { 700 | "version": "2.3.0", 701 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 702 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 703 | "dev": true, 704 | "requires": { 705 | "ee-first": "1.1.1" 706 | } 707 | }, 708 | "on-headers": { 709 | "version": "1.0.1", 710 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 711 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", 712 | "dev": true 713 | }, 714 | "once": { 715 | "version": "1.4.0", 716 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 717 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 718 | "dev": true, 719 | "requires": { 720 | "wrappy": "1.0.2" 721 | } 722 | }, 723 | "only": { 724 | "version": "0.0.2", 725 | "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", 726 | "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", 727 | "dev": true 728 | }, 729 | "parseurl": { 730 | "version": "1.3.1", 731 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 732 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", 733 | "dev": true 734 | }, 735 | "path-is-absolute": { 736 | "version": "1.0.1", 737 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 738 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 739 | "dev": true 740 | }, 741 | "path-parse": { 742 | "version": "1.0.5", 743 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 744 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 745 | "dev": true 746 | }, 747 | "path-to-regexp": { 748 | "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 749 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 750 | "dev": true, 751 | "requires": { 752 | "isarray": "0.0.1" 753 | } 754 | }, 755 | "proxy-addr": { 756 | "version": "1.1.5", 757 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", 758 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 759 | "dev": true, 760 | "requires": { 761 | "forwarded": "0.1.0", 762 | "ipaddr.js": "1.4.0" 763 | } 764 | }, 765 | "qs": { 766 | "version": "6.4.0", 767 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 768 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", 769 | "dev": true 770 | }, 771 | "range-parser": { 772 | "version": "1.2.0", 773 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 774 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", 775 | "dev": true 776 | }, 777 | "raw-body": { 778 | "version": "2.2.0", 779 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 780 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", 781 | "dev": true, 782 | "requires": { 783 | "bytes": "2.4.0", 784 | "iconv-lite": "0.4.15", 785 | "unpipe": "1.0.0" 786 | } 787 | }, 788 | "resolve": { 789 | "version": "1.3.3", 790 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", 791 | "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", 792 | "dev": true, 793 | "requires": { 794 | "path-parse": "1.0.5" 795 | } 796 | }, 797 | "safe-buffer": { 798 | "version": "5.0.1", 799 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", 800 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", 801 | "dev": true 802 | }, 803 | "semver": { 804 | "version": "5.3.0", 805 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 806 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 807 | "dev": true 808 | }, 809 | "send": { 810 | "version": "0.15.3", 811 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 812 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=", 813 | "dev": true, 814 | "requires": { 815 | "debug": "2.6.7", 816 | "depd": "1.1.0", 817 | "destroy": "1.0.4", 818 | "encodeurl": "1.0.1", 819 | "escape-html": "1.0.3", 820 | "etag": "1.8.0", 821 | "fresh": "0.5.0", 822 | "http-errors": "1.6.1", 823 | "mime": "1.3.4", 824 | "ms": "2.0.0", 825 | "on-finished": "2.3.0", 826 | "range-parser": "1.2.0", 827 | "statuses": "1.3.1" 828 | } 829 | }, 830 | "serve-static": { 831 | "version": "1.12.3", 832 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 833 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=", 834 | "dev": true, 835 | "requires": { 836 | "encodeurl": "1.0.1", 837 | "escape-html": "1.0.3", 838 | "parseurl": "1.3.1", 839 | "send": "0.15.3" 840 | } 841 | }, 842 | "setprototypeof": { 843 | "version": "1.0.3", 844 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 845 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", 846 | "dev": true 847 | }, 848 | "statuses": { 849 | "version": "1.3.1", 850 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 851 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", 852 | "dev": true 853 | }, 854 | "strip-ansi": { 855 | "version": "3.0.1", 856 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 857 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 858 | "dev": true, 859 | "requires": { 860 | "ansi-regex": "2.1.1" 861 | } 862 | }, 863 | "tslib": { 864 | "version": "1.7.1", 865 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", 866 | "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=", 867 | "dev": true 868 | }, 869 | "tslint": { 870 | "version": "5.5.0", 871 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.5.0.tgz", 872 | "integrity": "sha1-EOjas+MGH6YelELozuOYKs8gpqo=", 873 | "dev": true, 874 | "requires": { 875 | "babel-code-frame": "6.22.0", 876 | "colors": "1.1.2", 877 | "commander": "2.9.0", 878 | "diff": "3.3.0", 879 | "glob": "7.1.2", 880 | "minimatch": "3.0.4", 881 | "resolve": "1.3.3", 882 | "semver": "5.3.0", 883 | "tslib": "1.7.1", 884 | "tsutils": "2.8.0" 885 | }, 886 | "dependencies": { 887 | "minimatch": { 888 | "version": "3.0.4", 889 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 890 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 891 | "dev": true, 892 | "requires": { 893 | "brace-expansion": "1.1.8" 894 | } 895 | } 896 | } 897 | }, 898 | "tsutils": { 899 | "version": "2.8.0", 900 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.0.tgz", 901 | "integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=", 902 | "dev": true, 903 | "requires": { 904 | "tslib": "1.7.1" 905 | } 906 | }, 907 | "type-is": { 908 | "version": "1.6.15", 909 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 910 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 911 | "dev": true, 912 | "requires": { 913 | "media-typer": "0.3.0", 914 | "mime-types": "2.1.16" 915 | } 916 | }, 917 | "typescript": { 918 | "version": "2.4.2", 919 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", 920 | "integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", 921 | "dev": true 922 | }, 923 | "unpipe": { 924 | "version": "1.0.0", 925 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 926 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 927 | "dev": true 928 | }, 929 | "utils-merge": { 930 | "version": "1.0.0", 931 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 932 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", 933 | "dev": true 934 | }, 935 | "vary": { 936 | "version": "1.1.1", 937 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 938 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=", 939 | "dev": true 940 | }, 941 | "wrappy": { 942 | "version": "1.0.2", 943 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 944 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 945 | "dev": true 946 | }, 947 | "yaku": { 948 | "version": "0.18.2", 949 | "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.18.2.tgz", 950 | "integrity": "sha512-Snn1wKdnoQWqWRWtpewDik97pJsNzp5bNnUBXxxtZPnzgiw/Xn3hjqqyPWbkygX3jWFMB/hw1gSaOWBn5vd/XQ==" 951 | } 952 | } 953 | } 954 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noflow", 3 | "version": "0.8.11", 4 | "description": "A minimal server middleware composer for the future.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "no": "no", 8 | "test": "no test", 9 | "prepublish": "no build" 10 | }, 11 | "nofile": { 12 | "autoInstallDeps": true 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ysmood/noflow" 17 | }, 18 | "files": [ 19 | "lib/*.{js,d.ts}" 20 | ], 21 | "keywords": [ 22 | "api", 23 | "app", 24 | "server", 25 | "http", 26 | "router", 27 | "web", 28 | "promise", 29 | "async", 30 | "await", 31 | "es7" 32 | ], 33 | "author": "http://ysmood.org", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/ysmood/noflow/issues" 37 | }, 38 | "homepage": "https://github.com/ysmood/noflow", 39 | "dependencies": { 40 | "yaku": "0.18.2" 41 | }, 42 | "devDependencies": { 43 | "@types/node": "8.0.16", 44 | "body-parser": "1.17.2", 45 | "cookie-session": "^2.0.0-beta.2", 46 | "express": "4.15.3", 47 | "junit": "1.4.9", 48 | "koa": "2.3.0", 49 | "nokit": "0.24.5", 50 | "path-to-regexp": "1.7.0", 51 | "send": "0.15.3", 52 | "tslint": "5.5.0", 53 | "typescript": "2.4.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NoFlow 2 | 3 | A minimal server middleware composer for the future. 4 | Mostly, It is used to create http server and handle proxy. 5 | The interesting part is that it also works fine without any ES6 or ES7 syntax, 6 | it's up to you to decide how fancy it will be. And because its middlewares are just normal 7 | functions, they can be easily composed with each other. 8 | 9 | To use noflow, you only have to remember a single rule "Any async function should and will return a Promise". 10 | 11 | 12 | 13 | # Features 14 | 15 | - Super lightweight, only one dependency, 200 sloc, learn it in 5 minutes 16 | - Static-typed with typescript 17 | - Faster than Express.js and Koa, see the benchmark section 18 | - Based on Promise, works well with async/await 19 | - Supports almost all exist Express-like middlewares 20 | 21 | 22 | **For examples, goto folder [examples](examples)**. 23 | 24 | To run the examples, you have to install the dependencies of this project: `npm i`. 25 | Such as, to run the [examples/basic.js](examples/basic.js), use command like: 26 | `node_modules/.bin/noe examples/basic.js` 27 | 28 | [![NPM version](https://badge.fury.io/js/noflow.svg)](http://badge.fury.io/js/noflow) [![Build Status](https://travis-ci.org/ysmood/noflow.svg)](https://travis-ci.org/ysmood/noflow) [![Build status](https://ci.appveyor.com/api/projects/status/github/ysmood/noflow?svg=true)](https://ci.appveyor.com/project/ysmood/noflow) [![Deps Up to Date](https://david-dm.org/ysmood/noflow.svg?style=flat)](https://david-dm.org/ysmood/noflow) 29 | 30 | 31 | 32 | # Quick Start 33 | 34 | Install it: `npm i noflow`. 35 | 36 | 37 | 38 | ### Hello World Example 39 | 40 | ```javascript 41 | import flow from "noflow"; 42 | 43 | let app = flow(); 44 | 45 | // Anything pushed into the app will be converted to a 46 | // middleware object sanely, even it's a string, buffer, stream or anything else. 47 | // Here we created a server that responses only string "hello world". 48 | app.push("hello world"); 49 | 50 | app.listen(8123); 51 | ``` 52 | 53 | 54 | 55 | ### ES5 56 | 57 | Without ES7, you can still have all the goodies. 58 | 59 | ```javascript 60 | var flow = require("noflow").default; 61 | 62 | var app = flow(); 63 | 64 | app.push(function ($) { 65 | return $.next().then(function () { 66 | console.log("done"); 67 | }); 68 | }); 69 | 70 | app.push(function ($) { 71 | $.body = "hello world"; 72 | }); 73 | 74 | app.listen(8123); 75 | ``` 76 | 77 | 78 | 79 | ### ES7 80 | 81 | Designed for the future ES7. 82 | 83 | ```javascript 84 | import flow from "noflow"; 85 | 86 | let app = flow(); 87 | 88 | app.push( 89 | 90 | async ({ next }) => { 91 | await next(); 92 | console.log("done"); 93 | }, 94 | 95 | $ => $.body = "hello world" 96 | 97 | ); 98 | 99 | app.listen(8123); 100 | ``` 101 | 102 | 103 | 104 | ### Routes 105 | 106 | You can write routes quickly by using select interface of [NoKit](https://github.com/ysmood/nokit). 107 | 108 | ```javascript 109 | import flow from "noflow"; 110 | import kit from "nokit"; 111 | let { match, select } = kit.require("proxy"); 112 | 113 | let app = flow(); 114 | 115 | app.push( 116 | select("/test", $ => $.body = $.url), 117 | 118 | select( 119 | // Express.js like url selector. 120 | match("/items/:id"), 121 | $ => $.body = $.url.id 122 | ), 123 | 124 | select( 125 | { 126 | url: "/api", 127 | method: /GET|POST/ // route both GET and POST 128 | }, 129 | $ => $.body = $.method + " " + $.url 130 | ), 131 | 132 | select( 133 | { 134 | // route some special headers 135 | headers: { 136 | host: "a.com" 137 | } 138 | }, 139 | $ => $.body = "ok" 140 | ) 141 | ); 142 | 143 | app.listen(8123); 144 | ``` 145 | 146 | 147 | 148 | ### Express middleware 149 | 150 | It's easy to convert an express middleware to noflow middleware. 151 | 152 | ```javascript 153 | import flow from "noflow"; 154 | import bodyParser from "body-parser"; 155 | 156 | let app = flow(); 157 | 158 | let convert = (h) => ({ req, res, next }) => 159 | new Promise((resolve, reject) => 160 | h(req, res, (err) => { 161 | if (err) 162 | return reject(err); 163 | else 164 | return next().then(resolve); 165 | }) 166 | ); 167 | 168 | app.push( 169 | convert(bodyParser.json()), 170 | $ => { 171 | $.body = $.req.body; 172 | } 173 | ); 174 | 175 | app.listen(8123); 176 | ``` 177 | 178 | 179 | 180 | # API 181 | 182 | It's recommended to use typescript to check all the API details. 183 | 184 | - ## **[flow()](lib/index.ts?source#L63)** 185 | 186 | Create an array instance with some handy server helper methods. 187 | 188 | - **param**: 189 | 190 | FlowArray middlewares Optional. If not provided, the return type will be 191 | a FlowArray, else a middleware Function. 192 | 193 | - **return**: 194 | 195 | FlowArray | Function 196 | 197 | - **example**: 198 | 199 | ```js 200 | import flow from "noflow" 201 | let app = flow(); 202 | app.push("OK"); 203 | app.listen(8123).then(() => console.log("started")); 204 | ``` 205 | 206 | 207 | 208 | 209 | # Status code 210 | 211 | Noflow will auto handle status code `200`, `204`, `404` and `500` for you only if 212 | you haven't set the status code yourself. 213 | 214 | - `204`: If you don't set `$.body`, such as it's `null` or `undefined`. 215 | 216 | - `404`: If no middleware found. 217 | 218 | - `500`: If any middleware reject or throws an error. 219 | 220 | - `userDefined`: If user set the `$.res.statusCode` manually. 221 | 222 | - `200`: If none of the above happens. 223 | 224 | 225 | # Error handling 226 | 227 | A middleware can catch all the errors of the middlewares after it. 228 | 229 | With ES5, you can use it like normal promise error handling: 230 | 231 | ```js 232 | var flow = require("noflow").default; 233 | 234 | var app = flow(); 235 | 236 | app.push(function ($) { 237 | return $.next().catch(function (e) { 238 | $.body = e; 239 | }); 240 | }); 241 | 242 | app.push(function () { 243 | throw "error"; 244 | }); 245 | 246 | app.push(function () { 247 | // Same with the `throw "error"` 248 | return Promise.reject("error"); 249 | }); 250 | 251 | app.listen(8123); 252 | ``` 253 | 254 | With ES7, you can use try-catch directly: 255 | 256 | ```js 257 | import flow from "noflow"; 258 | 259 | let app = flow(); 260 | 261 | app.push(async ($) => { 262 | try { 263 | await $.next(); 264 | } catch (e) { 265 | $.body = e; 266 | } 267 | }); 268 | 269 | app.push(() => { 270 | throw "error"; 271 | }); 272 | 273 | app.listen(8123); 274 | ``` 275 | 276 | 277 | # [NoKit](https://github.com/ysmood/nokit) 278 | 279 | Noflow relies on the async nature of Promise, when you need async io tools, nokit will be the best choice. 280 | nokit has all the commonly used IO functions with Promise support. 281 | 282 | For example you can use them seamlessly: 283 | 284 | ```js 285 | import flow from "noflow"; 286 | import kit from "nokit"; 287 | let { select } = kit.require("proxy"); 288 | 289 | let app = flow(); 290 | 291 | app.push( 292 | select( 293 | "/a", 294 | kit.readJson("a.json") // readJson returns a Promise 295 | ), 296 | 297 | select("/b", async $ => { 298 | let txt = await kit.readFile("b.txt"); 299 | let data = await kit.request("http://test.com/" + $.url); 300 | $.body = txt + data; 301 | }) 302 | ); 303 | 304 | app.listen(8123).then(() => { 305 | kit.request('127.0.0.1:8123/a').then(kit.logs); 306 | }); 307 | ``` 308 | 309 | # Benchmark 310 | 311 | These comparisons only reflect some limited truth, no one is better than all others on all aspects. 312 | You can run it yourself in terminal: `npm run no -- benchmark`. 313 | 314 | ``` 315 | Node v5.4.0 316 | The less the better: 317 | noflow: 1839.333ms 318 | koa: 2598.171ms 319 | express: 3239.013ms 320 | ``` 321 | -------------------------------------------------------------------------------- /test/basic.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Promise from "yaku"; 4 | import testSuit from "./testSuit"; 5 | import * as https from "https"; 6 | 7 | module.exports = testSuit("basic", ({ 8 | it, request, eq, flow, kit 9 | }) => { 10 | 11 | it("hello world", () => { 12 | let app = flow(); 13 | 14 | app.push("hello world"); 15 | 16 | return eq(request(app)(), "hello world"); 17 | }); 18 | 19 | it("should print the 'hello world' by given handler", () => { 20 | let app = flow(); 21 | 22 | app.push($ => { 23 | $.body = "hello world"; 24 | }); 25 | 26 | return eq(request(app)(), "hello world"); 27 | }); 28 | 29 | it("should echo the request string by given handler", () => { 30 | let app = flow(); 31 | let proxy = kit.require("proxy"); 32 | 33 | app.push(proxy.body()); 34 | 35 | app.push($ => { 36 | $.body = "echo:" + $["reqBody"]; 37 | }); 38 | 39 | return eq( 40 | request(app)({url: "/", reqData: "XtX5cRfGIC"}), 41 | "echo:XtX5cRfGIC" 42 | ); 43 | }); 44 | 45 | it("should echo the JSON object by given handler", () => { 46 | let app = flow(); 47 | let obj = { 48 | prop1: 10, 49 | prop2: "prop2", 50 | prop3: { 51 | subprop1: 1.0, 52 | subprop2: null, 53 | subprop3: true, 54 | subprop4: false 55 | } 56 | }; 57 | 58 | app.push($ => { 59 | $.body = obj; 60 | }); 61 | 62 | return request(app)({url: "/"}).then((respObj) => { 63 | return eq(obj, JSON.parse(respObj)); 64 | }); 65 | }); 66 | 67 | it("should response with text/plain content type", () => { 68 | let app = flow(); 69 | let obj = 10; 70 | 71 | app.push($ => { 72 | $.body = obj; 73 | }); 74 | 75 | return request(app)({url: "/", body: false}).then((resp) => { 76 | return eq(resp.headers["content-type"], "text/plain"); 77 | }); 78 | }); 79 | 80 | it("should response with application/json content type", () => { 81 | let app = flow(); 82 | let obj = [1, 2, 3]; 83 | 84 | app.push($ => { 85 | $.body = obj; 86 | }); 87 | 88 | return request(app)({url: "/", body: false}).then((resp) => { 89 | return eq(resp.headers["content-type"], "application/json; charset=utf-8"); 90 | }); 91 | }); 92 | 93 | it("should response with application/json content type", () => { 94 | let app = flow(); 95 | let obj = { 96 | prop1: 10 97 | }; 98 | 99 | app.push($ => { 100 | $.body = obj; 101 | }); 102 | 103 | return request(app)({url: "/", body: false}).then((resp) => { 104 | return eq(resp.headers["content-type"], "application/json; charset=utf-8"); 105 | }); 106 | }); 107 | 108 | it("should response with application/octet-stream content type", () => { 109 | let app = flow(); 110 | let obj = new Buffer([1, 2, 3]); 111 | 112 | app.push($ => { 113 | $.body = obj; 114 | }); 115 | 116 | return request(app)({url: "/", body: false}).then((resp) => { 117 | return eq(resp.headers["content-type"], "application/octet-stream"); 118 | }); 119 | }); 120 | 121 | it("should response with text/html content type", () => { 122 | let app = flow(); 123 | let obj = "test"; 124 | 125 | app.push($ => { 126 | $.body = obj; 127 | }); 128 | 129 | return request(app)({url: "/", body: false}).then((resp) => { 130 | return eq(resp.headers["content-type"], "text/html; charset=utf-8"); 131 | }); 132 | }); 133 | 134 | it("should response with given content length", () => { 135 | const FIX = 5; 136 | 137 | let app = flow(); 138 | let buf = new Buffer(FIX); 139 | 140 | app.push($ => { 141 | $.body = buf; 142 | }); 143 | 144 | return request(app)({url: "/", body: false}).then((resp) => { 145 | return eq(+resp.headers["content-length"], FIX); 146 | }); 147 | }); 148 | 149 | it("should response あおい with content length 9", () => { 150 | let app = flow(); 151 | app.push("あおい"); 152 | 153 | return request(app)({url: "/", body: false}).then((res) => { 154 | return eq(res.headers["content-length"], "9"); 155 | }); 156 | }); 157 | 158 | it("should echo the promise by given handler", () => { 159 | let app = flow(); 160 | 161 | app.push($ => { 162 | $.body = kit.readFile("package.json"); 163 | }); 164 | 165 | return eq(request(app)({url: "/", resEncoding: null }), kit.readFile("package.json")); 166 | }); 167 | 168 | it("should echo the stream by given handler", () => { 169 | let app = flow(); 170 | 171 | app.push($ => { 172 | $.body = kit.createReadStream("package.json"); 173 | }); 174 | 175 | return eq(request(app)({url: "/", resEncoding: null }), kit.readFile("package.json")); 176 | }); 177 | 178 | it("should response `null` with status code 204", () => { 179 | let app = flow(); 180 | app.push(null); 181 | 182 | return eq(request(app)({url: "/", body: false}).then(e => e.statusCode), 204); 183 | }); 184 | 185 | it("should response `undefined` with status code 204", () => { 186 | let app = flow(); 187 | app.push($ => $.body = undefined); 188 | 189 | return eq(request(app)({url: "/", body: false}).then(e => e.statusCode), 204); 190 | }); 191 | 192 | it("should response `null` with status code 204", () => { 193 | let app = flow(); 194 | app.push($ => $.body = null); 195 | 196 | return eq(request(app)({url: "/", body: false}).then(e => e.statusCode), 204); 197 | }); 198 | 199 | it("should echo the buffer by given handler", () => { 200 | let app = flow(); 201 | 202 | return kit.readFile("package.json").then((buf) => { 203 | app.push($ => { 204 | $.body = buf; 205 | }); 206 | 207 | return eq( 208 | request(app)({url: "/", resEncoding: null }), 209 | buf 210 | ); 211 | }) 212 | }); 213 | 214 | it("should work with https", (after) => { 215 | let opts = { 216 | key: kit.readFileSync("test/testSuit/server.key"), 217 | cert: kit.readFileSync("test/testSuit/server.crt") 218 | }; 219 | 220 | let server; 221 | 222 | after(() => { 223 | server.close(); 224 | }); 225 | 226 | return new Promise((resolve) => { 227 | server = https.createServer(opts, flow(["hello world"])) 228 | .listen(0, () => { 229 | let { port } = server.address(); 230 | 231 | resolve(it.eq(kit.request({ 232 | url: `https://127.0.0.1:${port}`, 233 | rejectUnauthorized: false 234 | }), "hello world")); 235 | }); 236 | }); 237 | 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /test/compose.ts: -------------------------------------------------------------------------------- 1 | import testSuit from "./testSuit"; 2 | import Promise from "yaku"; 3 | 4 | export default testSuit("compose", ({ 5 | it, request, eq, flow 6 | }) => { 7 | 8 | it("basic", () => { 9 | let app = flow(); 10 | 11 | let c = 0; 12 | app.push( 13 | $ => (c++, $.next()), 14 | flow( 15 | $ => (c++, $.next()), 16 | flow( 17 | $ => (c++, $.next()), 18 | $ => (c++, $.next()) 19 | ) 20 | ), 21 | $ => $.body = c 22 | ); 23 | 24 | return eq(request(app)(), "4"); 25 | }); 26 | 27 | it("arguments", () => { 28 | let app = flow(); 29 | 30 | app.push( 31 | ({ next }) => next(), 32 | flow( 33 | ({ next }) => next(), 34 | $ => $.body = "final" 35 | ) 36 | ); 37 | 38 | return eq(request(app)(), "final"); 39 | }); 40 | 41 | it("parent catch composed error", () => { 42 | let app = flow(); 43 | 44 | app.push( 45 | $ => { 46 | return $.next().catch((err) => { 47 | $.body = `catch ${err}`; 48 | }); 49 | }, 50 | flow([ 51 | () => { throw "err"; } 52 | ]) 53 | ); 54 | 55 | return eq(request(app)(), "catch err"); 56 | }); 57 | 58 | it("order by decreasing time", () => { 59 | let app = flow(); 60 | 61 | let c = ""; 62 | 63 | let p1 = new Promise((resolve) => { 64 | setTimeout(() => resolve("p1"), 200); 65 | }); 66 | 67 | let p2 = new Promise((resolve) => { 68 | setTimeout(() => resolve("p2"), 100); 69 | }); 70 | 71 | app.push( 72 | $ => { 73 | return p1.then((v) => { 74 | c += v 75 | return $.next(); 76 | }); 77 | }, 78 | $ => { 79 | return p2.then((v) => { 80 | c += v 81 | return $.next(); 82 | }); 83 | }, 84 | $ => { 85 | $.body = c; 86 | } 87 | ); 88 | 89 | return eq(request(app)(), "p1p2"); 90 | 91 | }); 92 | 93 | it("order by increasing time", () => { 94 | let app = flow(); 95 | 96 | let c = ""; 97 | 98 | let p1 = new Promise((resolve) => { 99 | setTimeout(() => resolve("p1"), 100); 100 | }); 101 | 102 | let p2 = new Promise((resolve) => { 103 | setTimeout(() => resolve("p2"), 200); 104 | }); 105 | 106 | app.push( 107 | $ => { 108 | return p1.then((v) => { 109 | c += v 110 | return $.next(); 111 | }); 112 | }, 113 | $ => { 114 | return p2.then((v) => { 115 | c += v 116 | return $.next(); 117 | }); 118 | }, 119 | $ => { 120 | $.body = c; 121 | } 122 | ); 123 | 124 | return eq(request(app)(), "p1p2"); 125 | 126 | }); 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /test/error.ts: -------------------------------------------------------------------------------- 1 | import testSuit from "./testSuit"; 2 | import Promise from "yaku"; 3 | 4 | export default testSuit("error", ({ 5 | it, request, eq, flow 6 | }) => { 7 | 8 | it("basic", () => { 9 | let app = flow(); 10 | 11 | app.push( 12 | $ => { 13 | return $.next().catch((err) => { 14 | $.body = "catch:" + err; 15 | }); 16 | }, 17 | () => { 18 | throw "error"; 19 | } 20 | ); 21 | 22 | return eq(request(app)(), "catch:error"); 23 | }); 24 | 25 | it("status code 500 with error message", () => { 26 | let app = flow(); 27 | 28 | app.push( 29 | () => { 30 | throw "error"; 31 | } 32 | ); 33 | 34 | let out = request(app)({ url: "/", body: false }).then(res => [res.statusCode, res.body]); 35 | 36 | return eq(out, [500, "error"]); 37 | }); 38 | 39 | it("status code 500 with circle body object", () => { 40 | let app = flow(); 41 | 42 | // circle object 43 | let body = { next: null }; 44 | body.next = body; 45 | app.push($ => $.body = body); 46 | 47 | let out = request(app)({ url: "/", body: false }).then(res => res.statusCode); 48 | 49 | return eq(out, 500); 50 | }); 51 | 52 | it("status code 404 with missing middlewares", () => { 53 | let app = flow(); 54 | let out = request(app)({ url: "/", body: false }).then(res => res.statusCode); 55 | return eq(out, 404); 56 | }); 57 | 58 | // TODO: move to middlewares independently 59 | it("timeout", () => { 60 | let app = flow(); 61 | 62 | app.push(($) => { 63 | let p1 = new Promise((resolve, reject) => { 64 | setTimeout( () => { 65 | $.body = "time out"; 66 | resolve(); 67 | }, 100); 68 | 69 | let p2 = $.next(); 70 | p2.then( (v) => resolve(v)).catch( (err) => reject(err) ); 71 | }); 72 | 73 | return p1; 74 | }, () => new Promise((r) => { 75 | setTimeout(r, 200); 76 | })); 77 | 78 | return eq(request(app)(), "time out"); 79 | }); 80 | }); -------------------------------------------------------------------------------- /test/testSuit/index.ts: -------------------------------------------------------------------------------- 1 | import flow, { Routes } from "../../lib"; 2 | 3 | let kit = require("nokit"); 4 | let { _ } = kit; 5 | global.Promise = kit.Promise; 6 | 7 | export interface Fn { 8 | (arg: { flow: typeof flow, kit: any, eq: any, it: any, request }): any 9 | } 10 | 11 | /** 12 | * It will let a noflow app instrance listen to a random port, then 13 | * request the port, then let the app close that port, then return a response 14 | * object. 15 | * `(app) => (url | { url, method, headers, reqData }) => Promise` 16 | */ 17 | let request = (app: Routes) => (opts) => 18 | app.listen().then(() => { 19 | let host = `http://127.0.0.1:${app.address().port}`; 20 | if (_.isString(opts)) 21 | opts = `${host}${opts}`; 22 | else if (_.isUndefined(opts)) 23 | opts = host + "/"; 24 | else 25 | opts.url = `${host}${opts.url}`; 26 | 27 | return kit.request(opts).then((res) => { 28 | return app.close().then(() => { 29 | return res; 30 | }); 31 | }); 32 | }); 33 | 34 | export default (title, fn: Fn) => 35 | (it) => 36 | it.describe(title, (it) => 37 | fn({ 38 | flow, 39 | kit, 40 | eq: it.eq, 41 | it, 42 | request 43 | }) 44 | ); 45 | -------------------------------------------------------------------------------- /test/testSuit/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICxTCCAi6gAwIBAgIJAKCuRJUg2nrAMA0GCSqGSIb3DQEBBQUAMEwxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRQwEgYDVQQKEwtub2Zsb3cgdGVz 4 | dDESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTE2MDIwMzA3NTMyOVoXDTE2MDMwNDA3 5 | NTMyOVowTDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxFDASBgNV 6 | BAoTC25vZmxvdyB0ZXN0MRIwEAYDVQQDEwkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcN 7 | AQEBBQADgY0AMIGJAoGBAN++1M6LaDU6wTE5V8hvQrCyAVQp0zorlfL6NFeaeP/b 8 | 1a8aCiSl9vnwlbH6vLj/0HpI44N34hVHT3CEUCkVBOZqWypPuUMhLVMtSB8zNhmd 9 | 4Y5gupBGUoV7cv/XHHgFh8RmW7E8/L0Eg6g+CWsNU0N3drlGu5ccAmXZVwm+f7in 10 | AgMBAAGjga4wgaswHQYDVR0OBBYEFAsWvAKRM7p62R/vlIF2NyJgHc10MHwGA1Ud 11 | IwR1MHOAFAsWvAKRM7p62R/vlIF2NyJgHc10oVCkTjBMMQswCQYDVQQGEwJBVTET 12 | MBEGA1UECBMKU29tZS1TdGF0ZTEUMBIGA1UEChMLbm9mbG93IHRlc3QxEjAQBgNV 13 | BAMTCTEyNy4wLjAuMYIJAKCuRJUg2nrAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN 14 | AQEFBQADgYEAzzeZ3ZrxbjxE/1IgLFJs+kNCvLCpZzeAGbqG4GysQd73g4L9MjyQ 15 | mFvaPrpRwJqYgrmdK3obv6uCAoGKOD1GnX3RwMJdz/KRtdNGDlPieDXl2vGbJxGe 16 | YrNurcXcBx5U7uvog0o60FDR4BH1M9dxPCEMOkjauP7Aw9McjM70+KU= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /test/testSuit/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDfvtTOi2g1OsExOVfIb0KwsgFUKdM6K5Xy+jRXmnj/29WvGgok 3 | pfb58JWx+ry4/9B6SOODd+IVR09whFApFQTmalsqT7lDIS1TLUgfMzYZneGOYLqQ 4 | RlKFe3L/1xx4BYfEZluxPPy9BIOoPglrDVNDd3a5RruXHAJl2VcJvn+4pwIDAQAB 5 | AoGBAMUSVGZIK9UWlytAEq2cMeVoI/t5i27lDQIilw/CI2lYu/GqwdxdwheLbI6D 6 | Bs77oyjSInKdmSic0r+MCv7kYJbcdXXGG/Pni015XJD6/CoRnnFS6HO7gcSqt0HS 7 | 0tPvJ45Jp6R770U0/A1sJptb7L7zIiq0GncJP5TtRk6wI1cBAkEA+m64AvsxRlxs 8 | NwfRkwnfB0oA2SWuB3DHef9gDN8C2vp07QMtsrvyHSPWFSTZ/0k2LeRBODs07mj+ 9 | xf9zZG22BwJBAOS4Op37bAuITqojqUCs7MZGFGFGIENvKVP5/8mUsFGm/bBrKph7 10 | OMOR+lI+YmN0dX2COqZdbC88xkxBDuY0QGECQFeGhMd99V3LBYNCN0v1oj5Q7fT2 11 | x1kQYJdR2scE1mGkMWN4ver+5wKqOLNRGuOvOcFWSjkV8Ez24zxeKH1MHtcCQQCz 12 | TV3elcio7c3Fgblg02+3LB8z9ta6Jr3+cGE+9UqYaFI+6HR3Hzprzteve+qDzzuA 13 | DgTOgTSbf4hxJRt6CoJhAkBTG3dufWrHY5z9YNbP0Q3CxUBKRNIaD0XvqUGxEVKt 14 | 1QeCGTHRlAhBYPN34jf+TFLgLZNH6wbhPm2xoNikkwzj 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tsconfig-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true 6 | }, 7 | "exclude": [ 8 | "test", "benchmark", "node_modules", "examples" 9 | ] 10 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [true, "check-space"], 5 | "indent": [true, "spaces"], 6 | "no-duplicate-variable": true, 7 | "no-eval": true, 8 | "no-internal-module": true, 9 | "no-trailing-whitespace": true, 10 | "no-var-keyword": true, 11 | "one-line": [true, "check-open-brace", "check-whitespace"], 12 | "quotemark": [true, "double"], 13 | "semicolon": false, 14 | "triple-equals": [true, "allow-null-check"], 15 | "typedef-whitespace": [true, { 16 | "call-signature": "nospace", 17 | "index-signature": "nospace", 18 | "parameter": "nospace", 19 | "property-declaration": "nospace", 20 | "variable-declaration": "nospace" 21 | }], 22 | "variable-name": [true, "ban-keywords"], 23 | "whitespace": [true, 24 | "check-branch", 25 | "check-decl", 26 | "check-operator", 27 | "check-separator", 28 | "check-type" 29 | ] 30 | } 31 | } --------------------------------------------------------------------------------