refactor Application into a class

This commit is contained in:
Tejas Manohar 2015-10-13 01:19:42 -05:00
parent e8f79d43f9
commit 93ade5e2dd
23 changed files with 243 additions and 247 deletions

View file

@ -44,8 +44,8 @@ $ npm install koa
## Example
```js
const koa = require('koa');
const app = koa();
const Koa = require('koa');
const app = new Koa();
// logger

View file

@ -2,8 +2,8 @@
'use strict';
const http = require('http');
const koa = require('../..');
const app = koa();
const Koa = require('../..');
const app = new Koa();
app.experimental = true;

View file

@ -2,8 +2,8 @@
'use strict';
const http = require('http');
const koa = require('..');
const app = koa();
const Koa = require('..');
const app = new Koa();
// number of middleware

View file

@ -30,7 +30,7 @@ $ node my-koa-app.js
```js
var koa = require('koa');
var app = koa();
var app = new Koa();
app.use(function *(){
this.body = 'Hello World';
@ -56,7 +56,7 @@ app.listen(3000);
```js
var koa = require('koa');
var app = koa();
var app = new Koa();
// x-response-time
@ -106,7 +106,7 @@ app.listen(3000);
```js
var koa = require('koa');
var app = koa();
var app = new Koa();
app.listen(3000);
```
@ -115,7 +115,7 @@ app.listen(3000);
```js
var http = require('http');
var koa = require('koa');
var app = koa();
var app = new Koa();
http.createServer(app.callback()).listen(3000);
```
@ -125,7 +125,7 @@ http.createServer(app.callback()).listen(3000);
```js
var http = require('http');
var koa = require('koa');
var app = koa();
var app = new Koa();
http.createServer(app.callback()).listen(3000);
http.createServer(app.callback()).listen(3001);
```

View file

@ -24,24 +24,21 @@ const only = require('only');
const co = require('co');
/**
* Application prototype.
* Expose `Application` class.
* Inherits from `Emitter.prototype`.
*/
const app = Application.prototype;
module.exports = class Application extends Emitter {
/**
* Expose `Application`.
*/
module.exports = Application;
/**
/**
* Initialize a new `Application`.
*
* @api public
*/
function Application() {
constructor() {
super();
if (!(this instanceof Application)) return new Application;
this.env = process.env.NODE_ENV || 'development';
this.subdomainOffset = 2;
@ -49,15 +46,9 @@ function Application() {
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
}
/**
* Inherit from `Emitter.prototype`.
*/
Object.setPrototypeOf(Application.prototype, Emitter.prototype);
/**
/**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
@ -67,13 +58,13 @@ Object.setPrototypeOf(Application.prototype, Emitter.prototype);
* @api public
*/
app.listen = function(){
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
}
/**
/**
* Return JSON representation.
* We only bother showing settings.
*
@ -81,15 +72,18 @@ app.listen = function(){
* @api public
*/
app.inspect =
app.toJSON = function(){
toJSON() {
return only(this, [
'subdomainOffset',
'env'
]);
};
}
/**
inspect() {
return this.toJSON();
}
/**
* Use the given middleware `fn`.
*
* @param {GeneratorFunction} fn
@ -97,7 +91,7 @@ app.toJSON = function(){
* @api public
*/
app.use = function(fn){
use(fn) {
if (!this.experimental) {
// es7 async functions are allowed
assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
@ -105,9 +99,9 @@ app.use = function(fn){
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
};
}
/**
/**
* Return a request handler callback
* for node's native http server.
*
@ -115,7 +109,7 @@ app.use = function(fn){
* @api public
*/
app.callback = function(){
callback() {
const fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
@ -130,15 +124,15 @@ app.callback = function(){
respond.call(ctx);
}).catch(ctx.onerror);
}
};
}
/**
/**
* Initialize a new context.
*
* @api private
*/
app.createContext = function(req, res){
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
@ -154,16 +148,16 @@ app.createContext = function(req, res){
context.accept = request.accept = accepts(req);
context.state = {};
return context;
};
}
/**
/**
* Default error handler.
*
* @param {Error} err
* @api private
*/
app.onerror = function(err){
onerror(err) {
assert(err instanceof Error, `non-error thrown: ${err}`);
if (404 == err.status || err.expose) return;
@ -175,7 +169,9 @@ app.onerror = function(err){
console.error();
console.error(msg.replace(/^/gm, ' '));
console.error();
};
}
}
/**
* Response helper.

View file

@ -3,12 +3,12 @@
const request = require('supertest');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('app.context', function(){
const app1 = koa();
const app1 = new Koa();
app1.context.msg = 'hello';
const app2 = koa();
const app2 = new Koa();
it('should merge properties', function(done){
app1.use(function *(next){

View file

@ -3,11 +3,11 @@
const request = require('supertest');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('app', function(){
it('should handle socket errors', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
// triggers this.socket.writable == false
@ -25,7 +25,7 @@ describe('app', function(){
})
it('should not .writeHead when !socket.writable', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
// set .writable to false
@ -49,7 +49,7 @@ describe('app', function(){
it('should set development env when NODE_ENV missing', function(){
const NODE_ENV = process.env.NODE_ENV;
process.env.NODE_ENV = '';
const app = koa();
const app = new Koa();
process.env.NODE_ENV = NODE_ENV;
assert.equal(app.env, 'development');
})

View file

@ -1,11 +1,11 @@
'use strict';
const koa = require('../..');
const Koa = require('../..');
describe('app.inspect()', function(){
it('should work', function(){
const app = koa();
const app = new Koa();
const util = require('util');
const str = util.inspect(app);
})

View file

@ -2,12 +2,12 @@
'use strict';
const stderr = require('test-console').stderr;
const koa = require('../..');
const Koa = require('../..');
const AssertionError = require('assert').AssertionError;
describe('app.onerror(err)', function(){
it('should throw an error if a non-error is given', function(done){
const app = koa();
const app = new Koa();
try {
app.onerror('foo');
@ -22,7 +22,7 @@ describe('app.onerror(err)', function(){
})
it('should do nothing if status is 404', function(done){
const app = koa();
const app = new Koa();
const err = new Error();
err.status = 404;
@ -37,7 +37,7 @@ describe('app.onerror(err)', function(){
})
it('should do nothing if .silent', function(done){
const app = koa();
const app = new Koa();
app.silent = true;
const err = new Error();
@ -51,7 +51,7 @@ describe('app.onerror(err)', function(){
})
it('should log the error to stderr', function(done){
const app = koa();
const app = new Koa();
app.env = 'dev';
const err = new Error();
@ -67,7 +67,7 @@ describe('app.onerror(err)', function(){
})
it('should use err.toString() instad of err.stack', function(done){
const app = koa();
const app = new Koa();
app.env = 'dev';
const err = new Error('mock stack null');

View file

@ -3,12 +3,12 @@
const request = require('supertest');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('app.request', function(){
const app1 = koa();
const app1 = new Koa();
app1.request.message = 'hello';
const app2 = koa();
const app2 = new Koa();
it('should merge properties', function(done){
app1.use(function *(next){

View file

@ -4,13 +4,13 @@
const request = require('supertest');
const statuses = require('statuses');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
const fs = require('fs');
describe('app.respond', function(){
describe('when this.respond === false', function(){
it('should bypass app.respond', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = 'Hello';
@ -36,7 +36,7 @@ describe('app.respond', function(){
describe('when HEAD is used', function(){
it('should not respond with the body', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = 'Hello';
@ -57,7 +57,7 @@ describe('app.respond', function(){
})
it('should keep json headers', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = { hello: 'world' };
@ -78,7 +78,7 @@ describe('app.respond', function(){
})
it('should keep string headers', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = 'hello world';
@ -99,7 +99,7 @@ describe('app.respond', function(){
})
it('should keep buffer headers', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = new Buffer('hello world');
@ -120,7 +120,7 @@ describe('app.respond', function(){
})
it('should respond with a 404 if no body was set', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
@ -134,7 +134,7 @@ describe('app.respond', function(){
})
it('should respond with a 200 if body = ""', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = '';
@ -148,7 +148,7 @@ describe('app.respond', function(){
})
it('should not overwrite the content-type', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 200;
@ -166,7 +166,7 @@ describe('app.respond', function(){
describe('when no middleware are present', function(){
it('should 404', function(done){
const app = koa();
const app = new Koa();
const server = app.listen();
@ -178,7 +178,7 @@ describe('app.respond', function(){
describe('when res has already been written to', function(){
it('should not cause an app error', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
const res = this.res;
@ -209,7 +209,7 @@ describe('app.respond', function(){
})
it('should send the right body', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
const res = this.res;
@ -233,7 +233,7 @@ describe('app.respond', function(){
describe('when .body is missing', function(){
describe('with status=400', function(){
it('should respond with the associated status message', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 400;
@ -251,7 +251,7 @@ describe('app.respond', function(){
describe('with status=204', function(){
it('should respond without a body', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 204;
@ -274,7 +274,7 @@ describe('app.respond', function(){
describe('with status=205', function(){
it('should respond without a body', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 205;
@ -297,7 +297,7 @@ describe('app.respond', function(){
describe('with status=304', function(){
it('should respond without a body', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 304;
@ -320,7 +320,7 @@ describe('app.respond', function(){
describe('with custom status=700', function(){
it('should respond with the associated status message', function (done){
const app = koa();
const app = new Koa();
statuses['700'] = 'custom status';
app.use(function *(){
@ -343,7 +343,7 @@ describe('app.respond', function(){
describe('with custom statusMessage=ok', function(){
it('should respond with the custom status message', function (done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 200;
@ -366,7 +366,7 @@ describe('app.respond', function(){
describe('with custom status without message', function (){
it('should respond with the status code number', function (done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.res.statusCode = 701;
@ -384,7 +384,7 @@ describe('app.respond', function(){
describe('when .body is a null', function(){
it('should respond 204 by default', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = null;
@ -405,7 +405,7 @@ describe('app.respond', function(){
})
it('should respond 204 with status=200', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 200;
@ -427,7 +427,7 @@ describe('app.respond', function(){
})
it('should respond 205 with status=205', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 205;
@ -449,7 +449,7 @@ describe('app.respond', function(){
})
it('should respond 304 with status=304', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 304;
@ -473,7 +473,7 @@ describe('app.respond', function(){
describe('when .body is a string', function(){
it('should respond', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = 'Hello';
@ -489,7 +489,7 @@ describe('app.respond', function(){
describe('when .body is a Buffer', function(){
it('should respond', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = new Buffer('Hello');
@ -505,7 +505,7 @@ describe('app.respond', function(){
describe('when .body is a Stream', function(){
it('should respond', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = fs.createReadStream('package.json');
@ -527,7 +527,7 @@ describe('app.respond', function(){
})
it('should strip content-length when overwriting', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = 'hello';
@ -550,7 +550,7 @@ describe('app.respond', function(){
})
it('should keep content-length if not overwritten', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.length = fs.readFileSync('package.json').length;
@ -573,7 +573,7 @@ describe('app.respond', function(){
})
it('should keep content-length if overwritten with the same stream', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.length = fs.readFileSync('package.json').length;
@ -598,7 +598,7 @@ describe('app.respond', function(){
})
it('should handle errors', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.set('Content-Type', 'application/json; charset=utf-8');
@ -615,7 +615,7 @@ describe('app.respond', function(){
})
it('should handle errors when no content status', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 204;
@ -631,7 +631,7 @@ describe('app.respond', function(){
it('should handle all intermediate stream body errors', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = fs.createReadStream('does not exist');
@ -649,7 +649,7 @@ describe('app.respond', function(){
describe('when .body is an Object', function(){
it('should respond with json', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = { hello: 'world' };
@ -666,7 +666,7 @@ describe('app.respond', function(){
describe('when an error occurs', function(){
it('should emit "error" on the app', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
throw new Error('boom');
@ -684,7 +684,7 @@ describe('app.respond', function(){
describe('with an .expose property', function(){
it('should expose the message', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
const err = new Error('sorry!');
@ -702,7 +702,7 @@ describe('app.respond', function(){
describe('with a .status property', function(){
it('should respond with .status', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
const err = new Error('s3 explodes');
@ -718,7 +718,7 @@ describe('app.respond', function(){
})
it('should respond with 500', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
throw new Error('boom!');
@ -733,7 +733,7 @@ describe('app.respond', function(){
})
it('should be catchable', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
try {
@ -760,7 +760,7 @@ describe('app.respond', function(){
describe('when status and body property', function(){
it('should 200', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 304;
@ -777,7 +777,7 @@ describe('app.respond', function(){
})
it('should 204', function(done) {
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = 200;

View file

@ -3,12 +3,12 @@
const request = require('supertest');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('app.response', function(){
const app1 = koa();
const app1 = new Koa();
app1.response.msg = 'hello';
const app2 = koa();
const app2 = new Koa();
it('should merge properties', function(done){
app1.use(function *(next){

View file

@ -1,11 +1,11 @@
'use strict';
const koa = require('../..');
const Koa = require('../..');
describe('app.toJSON()', function(){
it('should work', function(){
const app = koa();
const app = new Koa();
const obj = app.toJSON();
obj.should.eql({

View file

@ -2,11 +2,11 @@
'use strict';
const request = require('supertest');
const koa = require('../..');
const Koa = require('../..');
describe('app.use(fn)', function(){
it('should compose middleware', function(done){
const app = koa();
const app = new Koa();
const calls = [];
app.use(function *(next){
@ -40,7 +40,7 @@ describe('app.use(fn)', function(){
})
it('should error when a non-generator function is passed', function(){
const app = koa();
const app = new Koa();
try {
app.use(function(){});
@ -50,7 +50,7 @@ describe('app.use(fn)', function(){
})
it('should not error when a non-generator function is passed when .experimental=true', function(){
const app = koa();
const app = new Koa();
app.experimental = true;
app.use(function(){});
})

View file

@ -2,11 +2,11 @@
'use strict';
const request = require('supertest');
const koa = require('../..');
const Koa = require('../..');
describe('ctx.cookies.set()', function(){
it('should set an unsigned cookie', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
this.cookies.set('name', 'jon');
@ -32,7 +32,7 @@ describe('ctx.cookies.set()', function(){
describe('with .signed', function(){
describe('when no .keys are set', function(){
it('should error', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
try {
@ -49,7 +49,7 @@ describe('ctx.cookies.set()', function(){
})
it('should send a signed cookie', function(done){
const app = koa();
const app = new Koa();
app.keys = ['a', 'b'];

View file

@ -2,11 +2,11 @@
'use strict';
const request = require('supertest');
const koa = require('../..');
const Koa = require('../..');
describe('ctx.onerror(err)', function(){
it('should respond', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
this.body = 'something else';
@ -25,7 +25,7 @@ describe('ctx.onerror(err)', function(){
})
it('should unset all headers', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
this.set('Vary', 'Accept-Encoding');
@ -55,7 +55,7 @@ describe('ctx.onerror(err)', function(){
describe('when invalid err.status', function(){
describe('not number', function(){
it('should respond 500', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
this.body = 'something else';
@ -76,7 +76,7 @@ describe('ctx.onerror(err)', function(){
describe('not http status code', function(){
it('should respond 500', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
this.body = 'something else';
@ -98,7 +98,7 @@ describe('ctx.onerror(err)', function(){
describe('when non-error thrown', function(){
it('should response non-error thrown message', function(done){
const app = koa();
const app = new Koa();
app.use(function *(next){
throw 'string error';

View file

@ -3,11 +3,11 @@
const request = require('supertest');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('ctx.state', function() {
it('should provide a ctx.state namespace', function(done) {
const app = koa();
const app = new Koa();
app.use(function *() {
assert.deepEqual(this.state, {});

View file

@ -6,11 +6,11 @@
*/
const request = require('supertest');
const koa = require('../..');
const Koa = require('../..');
describe('.experimental=true', function () {
it('should support async functions', function (done) {
const app = koa();
const app = new Koa();
app.experimental = true;
app.use(async function (next) {
const string = await Promise.resolve('asdf');

View file

@ -2,7 +2,7 @@
'use strict';
const Stream = require('stream');
const koa = require('../..');
const Koa = require('../..');
exports = module.exports = function(req, res){
const socket = new Stream.Duplex();
@ -11,7 +11,7 @@ exports = module.exports = function(req, res){
res.getHeader = function(k){ return res._headers[k.toLowerCase()] };
res.setHeader = function(k, v){ res._headers[k.toLowerCase()] = v };
res.removeHeader = function(k, v){ delete res._headers[k.toLowerCase()] };
return koa().createContext(req, res);
return (new Koa()).createContext(req, res);
}
exports.request = function(req, res){

View file

@ -3,7 +3,7 @@
const Stream = require('stream');
const http = require('http');
const koa = require('../../');
const Koa = require('../../');
const context = require('../helpers/context');
describe('ctx.href', function(){
@ -25,7 +25,7 @@ describe('ctx.href', function(){
})
it('should work with `GET http://example.com/foo`', function(done){
const app = koa()
const app = new Koa()
app.use(function* (){
this.body = this.href
})

View file

@ -3,7 +3,7 @@
const Stream = require('stream');
const http = require('http');
const koa = require('../../');
const Koa = require('../../');
const context = require('../helpers/context');
describe('ctx.origin', function(){

View file

@ -3,7 +3,7 @@
const context = require('../helpers/context');
const request = require('supertest');
const koa = require('../..');
const Koa = require('../..');
describe('ctx.attachment([filename])', function(){
describe('when given a filename', function(){
@ -32,7 +32,7 @@ describe('ctx.attachment([filename])', function(){
})
it('should work with http client', function(done){
const app = koa();
const app = new Koa();
app.use(function* (next){
this.attachment('path/to/include-no-ascii-char-中文名-ok.json')

View file

@ -5,7 +5,7 @@ const response = require('../helpers/context').response;
const request = require('supertest');
const statuses = require('statuses');
const assert = require('assert');
const koa = require('../..');
const Koa = require('../..');
describe('res.status=', function(){
describe('when a status code', function(){
@ -60,7 +60,7 @@ describe('res.status=', function(){
function strip(status) {
it('should strip content related header fields', function(done){
const app = koa();
const app = new Koa();
app.use(function *(){
this.body = { foo: 'bar' };
@ -86,7 +86,7 @@ describe('res.status=', function(){
})
it('should strip content releated header fields after status set', function(done) {
const app = koa();
const app = new Koa();
app.use(function *(){
this.status = status;