├── .gitattributes
├── .travis.yml
├── LICENSE
├── README.md
├── README_CN.md
├── lib
├── application.js
├── application.ts
├── context.js
├── context.ts
├── request.js
├── request.ts
├── response.js
└── response.ts
├── package-lock.json
├── package.json
├── source
├── logo small.png
├── logo.png
├── tkoa logo square.png
└── ts logo.png
├── test.js
└── tsconfig.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-language=JavaScript
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '7.6'
4 |
5 | cache:
6 | directories:
7 | - node_modules
8 |
9 |
10 | install:
11 | - npm i --global ava
12 | - npm i --save-dev ava
13 | - npm install --dependencies
14 |
15 | before_script:
16 |
17 | script:
18 | - npm run test
19 |
20 | branches:
21 | only:
22 | - master
23 | env:
24 | global:
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 tkoajs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 🌈 Tkoa is a Koa web app framework written in typescript. 
11 |
12 | Although written in typescript, you can still use the nodejs framework and koa middleware.
13 |
14 | Not only that, you can also enjoy the type checking and convenient testing with typescript!
15 |
16 | ## Installation
17 | TKoa requires **>= typescript v3.1.0** and **node v7.6.0** for ES2015 and async function support.
18 |
19 | ```shell
20 | $ npm install tkoa
21 | ```
22 |
23 | ### Hello T-koa
24 |
25 | ```typescript
26 | import tKoa = require('tkoa');
27 |
28 | interface ctx {
29 | res: {
30 | end: Function
31 | }
32 | }
33 |
34 | const app = new tKoa();
35 |
36 | // response
37 | app.use((ctx: ctx) => {
38 | ctx.res.end('Hello T-koa!');
39 | });
40 |
41 | app.listen(3000);
42 | ```
43 |
44 | ### Middleware
45 | Tkoa is a middleware framework that can take two different kinds of functions as middleware:
46 |
47 | - async function
48 | - common function
49 |
50 | Here is an example of logger middleware with each of the different functions:
51 |
52 | #### async functions (node v7.6+):
53 |
54 | ```typescript
55 | interface ctx {
56 | method: string,
57 | url: string
58 | }
59 |
60 | app.use(async (ctx: ctx, next: Function) => {
61 | const start = Date.now();
62 | await next();
63 | const ms = Date.now() - start;
64 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
65 | });
66 | ```
67 |
68 | #### Common function
69 | ```typescript
70 | // Middleware normally takes two parameters (ctx, next), ctx is the context for one request,
71 | // next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.
72 |
73 | interface ctx {
74 | method: string,
75 | url: string
76 | }
77 |
78 | app.use((ctx: ctx, next: Function) => {
79 | const start = Date.now();
80 | return next().then(() => {
81 | const ms = Date.now() - start;
82 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
83 | });
84 | });
85 | ```
86 |
87 | ## Getting started
88 | - [Tkoa - wiki](https://github.com/tkoajs/tkoa/wiki)
89 | - [zhcn - 中文文档](https://github.com/tkoajs/tkoa/blob/master/README_CN.md)
90 | - [Gitter](https://gitter.im/tkoa-js/community)
91 |
92 | ## Support
93 | ### TypeScript
94 | - Higher than version v3.1
95 | ### Node.js
96 | - Higher than version v7.6.0
97 |
98 | ## License
99 | [MIT](https://github.com/tkoajs/tkoa/blob/master/LICENSE)
100 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 🌈 Tkoa是使用 typescript 编写的 koa 框架! 
11 |
12 | 尽管它是基于 typescript 编写,但是你依然还是可以使用一些 node.js 框架和基于 koa 的中间件。
13 |
14 | 不仅如此,你还可以享受 typescript 的类型检查系统和方便地使用 typescript 进行测试!
15 |
16 | ## 安装
17 | TKoa 需要 **>= typescript v3.1.0** 和 **node v7.6.0** 版本。
18 |
19 | ```shell
20 | $ npm install tkoa
21 | ```
22 |
23 | ### Hello T-koa
24 |
25 | ```typescript
26 | import tKoa = require('tkoa');
27 |
28 | interface ctx {
29 | res: {
30 | end: Function
31 | }
32 | }
33 |
34 | const app = new tKoa();
35 |
36 | // 响应
37 | app.use((ctx: ctx) => {
38 | ctx.res.end('Hello T-koa!');
39 | });
40 |
41 | app.listen(3000);
42 | ```
43 |
44 | ### Middleware
45 | Tkoa 是一个中间件框架,拥有两种中间件:
46 |
47 | - 异步中间件
48 | - 普通中间件
49 |
50 | 下面是一个日志记录中间件示例,其中使用了不同的中间件类型:
51 |
52 | #### async functions (node v7.6+):
53 |
54 | ```typescript
55 | interface ctx {
56 | method: string,
57 | url: string
58 | }
59 |
60 | app.use(async (ctx: ctx, next: Function) => {
61 | const start = Date.now();
62 | await next();
63 | const ms = Date.now() - start;
64 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
65 | });
66 | ```
67 |
68 | #### Common function
69 | ```typescript
70 | // 中间件通常需要两个参数(ctx,next),ctx是一个请求的上下文,next是一个被调用来执行下游中间件的函数。它返回一个带有then函数的Promise,用于在完成后运行代码。
71 |
72 | interface ctx {
73 | method: string,
74 | url: string
75 | }
76 |
77 | app.use((ctx: ctx, next: Function) => {
78 | const start = Date.now();
79 | return next().then(() => {
80 | const ms = Date.now() - start;
81 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
82 | });
83 | });
84 | ```
85 |
86 | ## Getting started
87 | - [Tkoa - 教程](https://github.com/tkoajs/tkoa/wiki)
88 | - [en - english readme](https://github.com/tkoajs/tkoa/blob/master/README.md)
89 | - [Gitter - 聊天室](https://gitter.im/tkoa-js/community_中文)
90 |
91 | ## Support
92 | ### TypeScript
93 | - 大于等于 v3.1 版本
94 | ### Node.js
95 | - 大于等于 v7.6.0 版本
96 |
97 | ## License
98 | [MIT](https://github.com/tkoajs/tkoa/blob/master/LICENSE)
99 |
--------------------------------------------------------------------------------
/lib/application.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * Module dependencies.
4 | */
5 | const onFinished = require("on-finished");
6 | const compose = require("koa-compose");
7 | const isJSON = require("koa-is-json");
8 | const statuses = require("statuses");
9 | const Emitter = require("events");
10 | const util = require("util");
11 | const Stream = require("stream");
12 | const http = require("http");
13 | const only = require("only");
14 | const context = require("./context");
15 | const request = require("./request");
16 | const response = require("./response");
17 | const debug = require('debug')('koa:application');
18 | /**
19 | * Response helper.
20 | */
21 | function respond(ctx) {
22 | // allow bypassing koa
23 | if (false === ctx.respond)
24 | return;
25 | if (!ctx.writable)
26 | return;
27 | const res = ctx.res;
28 | let body = ctx.body;
29 | const code = ctx.status;
30 | // ignore body
31 | if (statuses.empty[code]) {
32 | // strip headers
33 | ctx.body = null;
34 | return res.end();
35 | }
36 | if ('HEAD' == ctx.method) {
37 | if (!res.headersSent && isJSON(body)) {
38 | ctx.length = Buffer.byteLength(JSON.stringify(body));
39 | }
40 | return res.end();
41 | }
42 | // status body
43 | if (null == body) {
44 | if (ctx.req.httpVersionMajor >= 2) {
45 | body = String(code);
46 | }
47 | else {
48 | body = ctx.message || String(code);
49 | }
50 | if (!res.headersSent) {
51 | ctx.type = 'text';
52 | ctx.length = Buffer.byteLength(body);
53 | }
54 | return res.end(body);
55 | }
56 | // responses
57 | if (Buffer.isBuffer(body))
58 | return res.end(body);
59 | if ('string' == typeof body)
60 | return res.end(body);
61 | if (body instanceof Stream)
62 | return body.pipe(res);
63 | // body: json
64 | body = JSON.stringify(body);
65 | if (!res.headersSent) {
66 | ctx.length = Buffer.byteLength(body);
67 | }
68 | res.end(body);
69 | }
70 | module.exports = class Application extends Emitter {
71 | constructor() {
72 | super();
73 | this.proxy = false;
74 | this.middleware = [];
75 | this.subdomainOffset = 2;
76 | this.env = process.env.NODE_ENV || 'development';
77 | this.context = Object.create(context);
78 | this.request = Object.create(request);
79 | this.response = Object.create(response);
80 | if (util.inspect.custom) {
81 | this[util.inspect.custom] = this.inspect;
82 | }
83 | }
84 | /**
85 | * Shorthand for:
86 | *
87 | * http.createServer(app.callback()).listen(...)
88 | *
89 | * @param {Mixed} ...
90 | * @return {Server}
91 | * @api public
92 | */
93 | listen(...args) {
94 | debug('listen');
95 | const server = http.createServer(this.callback());
96 | return server.listen(...args);
97 | }
98 | /**
99 | * Return JSON representation.
100 | * We only bother showing settings.
101 | *
102 | * @return {Object}
103 | * @api public
104 | */
105 | toJSON() {
106 | return only(this, [
107 | 'subdomainOffset',
108 | 'proxy',
109 | 'env'
110 | ]);
111 | }
112 | /**
113 | * Inspect implementation.
114 | *
115 | * @return {Object}
116 | * @api public
117 | */
118 | inspect() {
119 | return this.toJSON();
120 | }
121 | /**
122 | * Use the given middleware `fn`.
123 | *
124 | * Old-style middleware will not be converted.
125 | *
126 | * @param {Function} fn
127 | * @return {Application} self
128 | * @api public
129 | */
130 | use(fn) {
131 | debug('use %s', fn.name || '-');
132 | this.middleware.push(fn);
133 | return this;
134 | }
135 | /**
136 | * Return a request handler callback
137 | * for node's native http server.
138 | *
139 | * @return {Function}
140 | * @api public
141 | */
142 | callback() {
143 | const fn = compose(this.middleware);
144 | if (!this.listenerCount('error'))
145 | this.on('error', this.onerror);
146 | const handleRequest = (req, res) => {
147 | const ctx = this.createContext(req, res);
148 | return this.handleRequest(ctx, fn);
149 | };
150 | return handleRequest;
151 | }
152 | /**
153 | * Handle request in callback.
154 | *
155 | * @api private
156 | */
157 | handleRequest(ctx, fnMiddleware) {
158 | const res = ctx.res;
159 | res.statusCode = 404;
160 | const onerror = (err) => ctx.onerror(err);
161 | const handleResponse = () => respond(ctx);
162 | onFinished(res, onerror);
163 | return fnMiddleware(ctx).then(handleResponse).catch(onerror);
164 | }
165 | /**
166 | * Initialize a new context.
167 | *
168 | * @api private
169 | */
170 | createContext(req, res) {
171 | const context = Object.create(this.context);
172 | const request = context.request = Object.create(this.request);
173 | const response = context.response = Object.create(this.response);
174 | context.app = request.app = response.app = this;
175 | context.req = request.req = response.req = req;
176 | context.res = request.res = response.res = res;
177 | request.ctx = response.ctx = context;
178 | request.response = response;
179 | response.request = request;
180 | context.originalUrl = request.originalUrl = req.url;
181 | context.state = {};
182 | return context;
183 | }
184 | /**
185 | * Default error handler.
186 | *
187 | * @param {Error} err
188 | * @api private
189 | */
190 | onerror(err) {
191 | if (404 == err.status || err.expose)
192 | return;
193 | if (this.silent)
194 | return;
195 | const msg = err.stack || err.toString();
196 | console.error();
197 | console.error(msg.replace(/^/gm, ' '));
198 | console.error();
199 | }
200 | };
201 |
--------------------------------------------------------------------------------
/lib/application.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | import onFinished = require('on-finished');
8 | import compose = require('koa-compose');
9 | import isJSON = require('koa-is-json');
10 | import statuses = require('statuses');
11 | import Emitter = require('events');
12 | import util = require('util');
13 | import Stream = require('stream');
14 | import http = require('http');
15 | import only = require('only');
16 | import context = require('./context');
17 | import request = require('./request');
18 | import response = require('./response');
19 | const debug = require('debug')('koa:application');
20 |
21 | /**
22 | * Expose `Application` class.
23 | * Inherits from `Emitter.prototype`.
24 | */
25 |
26 | export = class Application extends Emitter {
27 |
28 | /**
29 | * Initialize a new `Application`.
30 | *
31 | * @api public
32 | */
33 |
34 | proxy: boolean;
35 | middleware: any[];
36 | subdomainOffset: number;
37 | env: string;
38 | context: any;
39 | request: any;
40 | response: any;
41 | silent: any;
42 |
43 | constructor() {
44 | super();
45 |
46 | this.proxy = false;
47 | this.middleware = [];
48 | this.subdomainOffset = 2;
49 | this.env = process.env.NODE_ENV || 'development';
50 | this.context = Object.create(context);
51 | this.request = Object.create(request);
52 | this.response = Object.create(response);
53 | if (util.inspect.custom) {
54 | this[util.inspect.custom] = this.inspect;
55 | }
56 | }
57 |
58 | /**
59 | * Shorthand for:
60 | *
61 | * http.createServer(app.callback()).listen(...)
62 | *
63 | * @param {Mixed} ...
64 | * @return {Server}
65 | * @api public
66 | */
67 |
68 | listen(...args: any[]): object {
69 | debug('listen');
70 | const server = http.createServer(this.callback());
71 | return server.listen(...args);
72 | }
73 |
74 | /**
75 | * Return JSON representation.
76 | * We only bother showing settings.
77 | *
78 | * @return {Object}
79 | * @api public
80 | */
81 |
82 | toJSON(): object {
83 | return only(this, [
84 | 'subdomainOffset',
85 | 'proxy',
86 | 'env'
87 | ]);
88 | }
89 |
90 | /**
91 | * Inspect implementation.
92 | *
93 | * @return {Object}
94 | * @api public
95 | */
96 |
97 | inspect(): object {
98 | return this.toJSON();
99 | }
100 |
101 | /**
102 | * Use the given middleware `fn`.
103 | *
104 | * Old-style middleware will not be converted.
105 | *
106 | * @param {Function} fn
107 | * @return {Application} self
108 | * @api public
109 | */
110 |
111 | use(fn: (ctx: object, next: Function) => void): this {
112 | debug('use %s', fn.name || '-');
113 | this.middleware.push(fn);
114 | return this;
115 | }
116 |
117 | /**
118 | * Return a request handler callback
119 | * for node's native http server.
120 | *
121 | * @return {Function}
122 | * @api public
123 | */
124 |
125 | callback(): object {
126 | const fn = compose(this.middleware);
127 |
128 | if (!this.listenerCount('error')) this.on('error', this.onerror);
129 |
130 | const handleRequest = (req: object, res: object) => {
131 | const ctx = this.createContext(req, res);
132 | return this.handleRequest(ctx, fn);
133 | };
134 |
135 | return handleRequest;
136 | }
137 |
138 | /**
139 | * Handle request in callback.
140 | *
141 | * @api private
142 | */
143 |
144 | handleRequest(ctx: any, fnMiddleware: any): any {
145 | const res = ctx.res;
146 | res.statusCode = 404;
147 | const onerror = (err: any) => ctx.onerror(err);
148 | const handleResponse = () => respond(ctx);
149 | onFinished(res, onerror);
150 | return fnMiddleware(ctx).then(handleResponse).catch(onerror);
151 | }
152 |
153 | /**
154 | * Initialize a new context.
155 | *
156 | * @api private
157 | */
158 |
159 | createContext(req: any, res: any): object {
160 | const context = Object.create(this.context);
161 | const request = context.request = Object.create(this.request);
162 | const response = context.response = Object.create(this.response);
163 | context.app = request.app = response.app = this;
164 | context.req = request.req = response.req = req;
165 | context.res = request.res = response.res = res;
166 | request.ctx = response.ctx = context;
167 | request.response = response;
168 | response.request = request;
169 | context.originalUrl = request.originalUrl = req.url;
170 | context.state = {};
171 | return context;
172 | }
173 |
174 | /**
175 | * Default error handler.
176 | *
177 | * @param {Error} err
178 | * @api private
179 | */
180 |
181 | onerror(err: any): void {
182 |
183 | if (404 == err.status || err.expose) return;
184 | if (this.silent) return;
185 |
186 | const msg = err.stack || err.toString();
187 | console.error();
188 | console.error(msg.replace(/^/gm, ' '));
189 | console.error();
190 | }
191 | }
192 |
193 | /**
194 | * Response helper.
195 | */
196 |
197 | function respond(ctx: any): any {
198 | // allow bypassing koa
199 | if (false === ctx.respond) return;
200 |
201 | if (!ctx.writable) return;
202 |
203 | const res = ctx.res;
204 | let body = ctx.body;
205 | const code = ctx.status;
206 |
207 | // ignore body
208 | if (statuses.empty[code]) {
209 | // strip headers
210 | ctx.body = null;
211 | return res.end();
212 | }
213 |
214 | if ('HEAD' == ctx.method) {
215 | if (!res.headersSent && isJSON(body)) {
216 | ctx.length = Buffer.byteLength(JSON.stringify(body));
217 | }
218 | return res.end();
219 | }
220 |
221 | // status body
222 | if (null == body) {
223 | if (ctx.req.httpVersionMajor >= 2) {
224 | body = String(code);
225 | } else {
226 | body = ctx.message || String(code);
227 | }
228 | if (!res.headersSent) {
229 | ctx.type = 'text';
230 | ctx.length = Buffer.byteLength(body);
231 | }
232 | return res.end(body);
233 | }
234 |
235 | // responses
236 | if (Buffer.isBuffer(body)) return res.end(body);
237 | if ('string' == typeof body) return res.end(body);
238 | if (body instanceof Stream) return body.pipe(res);
239 |
240 | // body: json
241 | body = JSON.stringify(body);
242 | if (!res.headersSent) {
243 | ctx.length = Buffer.byteLength(body);
244 | }
245 | res.end(body);
246 | }
247 |
--------------------------------------------------------------------------------
/lib/context.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * Module dependencies.
4 | */
5 | const util = require("util");
6 | const createError = require("http-errors");
7 | const httpAssert = require("http-assert");
8 | const delegate = require("delegates");
9 | const statuses = require("statuses");
10 | const Cookies = require("cookies");
11 | const COOKIES = Symbol('context#cookies');
12 | /**
13 | * Context prototype.
14 | */
15 | const proto = {
16 | /**
17 | * util.inspect() implementation, which
18 | * just returns the JSON output.
19 | *
20 | * @return {Object}
21 | * @api public
22 | */
23 | inspect() {
24 | if (this === proto)
25 | return this;
26 | return this.toJSON();
27 | },
28 | /**
29 | * Return JSON representation.
30 | *
31 | * Here we explicitly invoke .toJSON() on each
32 | * object, as iteration will otherwise fail due
33 | * to the getters and cause utilities such as
34 | * clone() to fail.
35 | *
36 | * @return {Object}
37 | * @api public
38 | */
39 | toJSON() {
40 | return {
41 | request: this.request.toJSON(),
42 | response: this.response.toJSON(),
43 | app: this.app.toJSON(),
44 | originalUrl: this.originalUrl,
45 | req: '',
46 | res: '',
47 | socket: ''
48 | };
49 | },
50 | /**
51 | * Similar to .throw(), adds assertion.
52 | *
53 | * this.assert(this.user, 401, 'Please login!');
54 | *
55 | * See: https://github.com/jshttp/http-assert
56 | *
57 | * @param {Mixed} test
58 | * @param {Number} status
59 | * @param {String} message
60 | * @api public
61 | */
62 | assert: httpAssert,
63 | /**
64 | * Throw an error with `status` (default 500) and
65 | * `msg`. Note that these are user-level
66 | * errors, and the message may be exposed to the client.
67 | *
68 | * this.throw(403)
69 | * this.throw(400, 'name required')
70 | * this.throw('something exploded')
71 | * this.throw(new Error('invalid'))
72 | * this.throw(400, new Error('invalid'))
73 | *
74 | * See: https://github.com/jshttp/http-errors
75 | *
76 | * Note: `status` should only be passed as the first parameter.
77 | *
78 | * @param {String|Number|Error} err, msg or status
79 | * @param {String|Number|Error} [err, msg or status]
80 | * @param {Object} [props]
81 | * @api public
82 | */
83 | throw(...args) {
84 | throw createError(...args);
85 | },
86 | /**
87 | * Default error handling.
88 | *
89 | * @param {Error} err
90 | * @api private
91 | */
92 | onerror(err) {
93 | // don't do anything if there is no error.
94 | // this allows you to pass `this.onerror`
95 | // to node-style callbacks.
96 | if (null == err)
97 | return;
98 | if (!(err instanceof Error))
99 | err = new Error(util.format('non-error thrown: %j', err));
100 | let headerSent = false;
101 | if (this.headerSent || !this.writable) {
102 | headerSent = err.headerSent = true;
103 | }
104 | // delegate
105 | this.app.emit('error', err, this);
106 | // nothing we can do here other
107 | // than delegate to the app-level
108 | // handler and log.
109 | if (headerSent) {
110 | return;
111 | }
112 | const { res } = this;
113 | // first unset all headers
114 | /* istanbul ignore else */
115 | if (typeof res.getHeaderNames === 'function') {
116 | res.getHeaderNames().forEach(name => res.removeHeader(name));
117 | }
118 | else {
119 | res._headers = {}; // Node < 7.7
120 | }
121 | // then set those specified
122 | this.set(err.headers);
123 | // force text/plain
124 | this.type = 'text';
125 | // ENOENT support
126 | if ('ENOENT' == err.code)
127 | err.status = 404;
128 | // default to 500
129 | if ('number' != typeof err.status || !statuses[err.status])
130 | err.status = 500;
131 | // respond
132 | const code = statuses[err.status];
133 | const msg = err.expose ? err.message : code;
134 | this.status = err.status;
135 | this.length = Buffer.byteLength(msg);
136 | res.end(msg);
137 | },
138 | get cookies() {
139 | if (!this[COOKIES]) {
140 | this[COOKIES] = new Cookies(this.req, this.res, {
141 | keys: this.app.keys,
142 | secure: this.request.secure
143 | });
144 | }
145 | return this[COOKIES];
146 | },
147 | set cookies(_cookies) {
148 | this[COOKIES] = _cookies;
149 | }
150 | };
151 | /**
152 | * Custom inspection implementation for newer Node.js versions.
153 | *
154 | * @return {Object}
155 | * @api public
156 | */
157 | /* istanbul ignore else */
158 | if (util.inspect.custom) {
159 | module.exports[util.inspect.custom] = module.exports.inspect;
160 | }
161 | /**
162 | * Response delegation.
163 | */
164 | delegate(proto, 'response')
165 | .method('attachment')
166 | .method('redirect')
167 | .method('remove')
168 | .method('vary')
169 | .method('set')
170 | .method('append')
171 | .method('flushHeaders')
172 | .access('status')
173 | .access('message')
174 | .access('body')
175 | .access('length')
176 | .access('type')
177 | .access('lastModified')
178 | .access('etag')
179 | .getter('headerSent')
180 | .getter('writable');
181 | /**
182 | * Request delegation.
183 | */
184 | delegate(proto, 'request')
185 | .method('acceptsLanguages')
186 | .method('acceptsEncodings')
187 | .method('acceptsCharsets')
188 | .method('accepts')
189 | .method('get')
190 | .method('is')
191 | .access('querystring')
192 | .access('idempotent')
193 | .access('socket')
194 | .access('search')
195 | .access('method')
196 | .access('query')
197 | .access('path')
198 | .access('url')
199 | .access('accept')
200 | .getter('origin')
201 | .getter('href')
202 | .getter('subdomains')
203 | .getter('protocol')
204 | .getter('host')
205 | .getter('hostname')
206 | .getter('URL')
207 | .getter('header')
208 | .getter('headers')
209 | .getter('secure')
210 | .getter('stale')
211 | .getter('fresh')
212 | .getter('ips')
213 | .getter('ip');
214 | module.exports = proto;
215 |
--------------------------------------------------------------------------------
/lib/context.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | import util = require('util');
8 | import createError = require('http-errors');
9 | import httpAssert = require('http-assert');
10 | import delegate = require('delegates');
11 | import statuses = require('statuses');
12 | import Cookies = require('cookies');
13 |
14 | const COOKIES = Symbol('context#cookies');
15 |
16 | /**
17 | * Context prototype.
18 | */
19 |
20 | const proto = {
21 |
22 | /**
23 | * util.inspect() implementation, which
24 | * just returns the JSON output.
25 | *
26 | * @return {Object}
27 | * @api public
28 | */
29 |
30 | inspect(): object {
31 | if (this === proto) return this;
32 | return this.toJSON();
33 | },
34 |
35 | /**
36 | * Return JSON representation.
37 | *
38 | * Here we explicitly invoke .toJSON() on each
39 | * object, as iteration will otherwise fail due
40 | * to the getters and cause utilities such as
41 | * clone() to fail.
42 | *
43 | * @return {Object}
44 | * @api public
45 | */
46 |
47 | toJSON(): object {
48 | return {
49 | request: this.request.toJSON(),
50 | response: this.response.toJSON(),
51 | app: this.app.toJSON(),
52 | originalUrl: this.originalUrl,
53 | req: '',
54 | res: '',
55 | socket: ''
56 | };
57 | },
58 |
59 | /**
60 | * Similar to .throw(), adds assertion.
61 | *
62 | * this.assert(this.user, 401, 'Please login!');
63 | *
64 | * See: https://github.com/jshttp/http-assert
65 | *
66 | * @param {Mixed} test
67 | * @param {Number} status
68 | * @param {String} message
69 | * @api public
70 | */
71 |
72 | assert: httpAssert,
73 |
74 | /**
75 | * Throw an error with `status` (default 500) and
76 | * `msg`. Note that these are user-level
77 | * errors, and the message may be exposed to the client.
78 | *
79 | * this.throw(403)
80 | * this.throw(400, 'name required')
81 | * this.throw('something exploded')
82 | * this.throw(new Error('invalid'))
83 | * this.throw(400, new Error('invalid'))
84 | *
85 | * See: https://github.com/jshttp/http-errors
86 | *
87 | * Note: `status` should only be passed as the first parameter.
88 | *
89 | * @param {String|Number|Error} err, msg or status
90 | * @param {String|Number|Error} [err, msg or status]
91 | * @param {Object} [props]
92 | * @api public
93 | */
94 |
95 | throw(...args: any[]): void {
96 | throw createError(...args);
97 | },
98 |
99 | /**
100 | * Default error handling.
101 | *
102 | * @param {Error} err
103 | * @api private
104 | */
105 |
106 | onerror(err: any): void {
107 | // don't do anything if there is no error.
108 | // this allows you to pass `this.onerror`
109 | // to node-style callbacks.
110 | if (null == err) return;
111 |
112 | if (!(err instanceof Error)) err = new Error(util.format('non-error thrown: %j', err));
113 |
114 | let headerSent = false;
115 | if (this.headerSent || !this.writable) {
116 | headerSent = err.headerSent = true;
117 | }
118 |
119 | // delegate
120 | this.app.emit('error', err, this);
121 |
122 | // nothing we can do here other
123 | // than delegate to the app-level
124 | // handler and log.
125 | if (headerSent) {
126 | return;
127 | }
128 |
129 | const { res } = this;
130 |
131 | // first unset all headers
132 | /* istanbul ignore else */
133 | if (typeof res.getHeaderNames === 'function') {
134 | res.getHeaderNames().forEach(name => res.removeHeader(name));
135 | } else {
136 | res._headers = {}; // Node < 7.7
137 | }
138 |
139 | // then set those specified
140 | this.set(err.headers);
141 |
142 | // force text/plain
143 | this.type = 'text';
144 |
145 | // ENOENT support
146 | if ('ENOENT' == err.code) err.status = 404;
147 |
148 | // default to 500
149 | if ('number' != typeof err.status || !statuses[err.status]) err.status = 500;
150 |
151 | // respond
152 | const code = statuses[err.status];
153 | const msg = err.expose ? err.message : code;
154 | this.status = err.status;
155 | this.length = Buffer.byteLength(msg);
156 | res.end(msg);
157 | },
158 |
159 | get cookies(): Cookies {
160 | if (!this[COOKIES]) {
161 | this[COOKIES] = new Cookies(this.req, this.res, {
162 | keys: this.app.keys,
163 | secure: this.request.secure
164 | });
165 | }
166 | return this[COOKIES];
167 | },
168 |
169 | set cookies(_cookies: Cookies) {
170 | this[COOKIES] = _cookies;
171 | }
172 | }
173 |
174 | export = proto;
175 |
176 | /**
177 | * Custom inspection implementation for newer Node.js versions.
178 | *
179 | * @return {Object}
180 | * @api public
181 | */
182 |
183 | /* istanbul ignore else */
184 | if (util.inspect.custom) {
185 | module.exports[util.inspect.custom] = module.exports.inspect;
186 | }
187 |
188 | /**
189 | * Response delegation.
190 | */
191 |
192 | delegate(proto, 'response')
193 | .method('attachment')
194 | .method('redirect')
195 | .method('remove')
196 | .method('vary')
197 | .method('set')
198 | .method('append')
199 | .method('flushHeaders')
200 | .access('status')
201 | .access('message')
202 | .access('body')
203 | .access('length')
204 | .access('type')
205 | .access('lastModified')
206 | .access('etag')
207 | .getter('headerSent')
208 | .getter('writable');
209 |
210 | /**
211 | * Request delegation.
212 | */
213 |
214 | delegate(proto, 'request')
215 | .method('acceptsLanguages')
216 | .method('acceptsEncodings')
217 | .method('acceptsCharsets')
218 | .method('accepts')
219 | .method('get')
220 | .method('is')
221 | .access('querystring')
222 | .access('idempotent')
223 | .access('socket')
224 | .access('search')
225 | .access('method')
226 | .access('query')
227 | .access('path')
228 | .access('url')
229 | .access('accept')
230 | .getter('origin')
231 | .getter('href')
232 | .getter('subdomains')
233 | .getter('protocol')
234 | .getter('host')
235 | .getter('hostname')
236 | .getter('URL')
237 | .getter('header')
238 | .getter('headers')
239 | .getter('secure')
240 | .getter('stale')
241 | .getter('fresh')
242 | .getter('ips')
243 | .getter('ip');
244 |
--------------------------------------------------------------------------------
/lib/request.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * Module dependencies.
5 | */
6 | const net = require("net");
7 | const accepts = require("accepts");
8 | const contentType = require("content-type");
9 | const parse = require("parseurl");
10 | const qs = require("querystring");
11 | const typeis = require("type-is");
12 | const fresh = require("fresh");
13 | const only = require("only");
14 | const util = require("util");
15 | const URL = require('url').URL;
16 | const stringify = require('url').format;
17 | const IP = Symbol('context#ip');
18 | /**
19 | * Prototype.
20 | */
21 | module.exports = {
22 | /**
23 | * Return request header.
24 | *
25 | * @return {Object}
26 | * @api public
27 | */
28 | get header() {
29 | return this.req.headers;
30 | },
31 | /**
32 | * Set request header.
33 | *
34 | * @api public
35 | */
36 | set header(val) {
37 | this.req.headers = val;
38 | },
39 | /**
40 | * Return request header, alias as request.header
41 | *
42 | * @return {Object}
43 | * @api public
44 | */
45 | get headers() {
46 | return this.req.headers;
47 | },
48 | /**
49 | * Set request header, alias as request.header
50 | *
51 | * @api public
52 | */
53 | set headers(val) {
54 | this.req.headers = val;
55 | },
56 | /**
57 | * Get request URL.
58 | *
59 | * @return {String}
60 | * @api public
61 | */
62 | get url() {
63 | return this.req.url;
64 | },
65 | /**
66 | * Set request URL.
67 | *
68 | * @api public
69 | */
70 | set url(val) {
71 | this.req.url = val;
72 | },
73 | /**
74 | * Get origin of URL.
75 | *
76 | * @return {String}
77 | * @api public
78 | */
79 | get origin() {
80 | return `${this.protocol}://${this.host}`;
81 | },
82 | /**
83 | * Get full request URL.
84 | *
85 | * @return {String}
86 | * @api public
87 | */
88 | get href() {
89 | // support: `GET http://example.com/foo`
90 | if (/^https?:\/\//i.test(this.originalUrl))
91 | return this.originalUrl;
92 | return this.origin + this.originalUrl;
93 | },
94 | /**
95 | * Get request method.
96 | *
97 | * @return {String}
98 | * @api public
99 | */
100 | get method() {
101 | return this.req.method;
102 | },
103 | /**
104 | * Set request method.
105 | *
106 | * @param {String} val
107 | * @api public
108 | */
109 | set method(val) {
110 | this.req.method = val;
111 | },
112 | /**
113 | * Get request pathname.
114 | *
115 | * @return {String}
116 | * @api public
117 | */
118 | get path() {
119 | return parse(this.req).pathname;
120 | },
121 | /**
122 | * Set pathname, retaining the query-string when present.
123 | *
124 | * @param {String} path
125 | * @api public
126 | */
127 | set path(path) {
128 | const url = parse(this.req);
129 | if (url.pathname === path)
130 | return;
131 | url.pathname = path;
132 | url.path = null;
133 | this.url = stringify(url);
134 | },
135 | /**
136 | * Get parsed query-string.
137 | *
138 | * @return {Object}
139 | * @api public
140 | */
141 | get query() {
142 | const str = this.querystring;
143 | const c = this._querycache = this._querycache || {};
144 | return c[str] || (c[str] = qs.parse(str));
145 | },
146 | /**
147 | * Set query-string as an object.
148 | *
149 | * @param {Object} obj
150 | * @api public
151 | */
152 | set query(obj) {
153 | this.querystring = qs.stringify(obj);
154 | },
155 | /**
156 | * Get query string.
157 | *
158 | * @return {String}
159 | * @api public
160 | */
161 | get querystring() {
162 | if (!this.req)
163 | return '';
164 | return parse(this.req).query || '';
165 | },
166 | /**
167 | * Set querystring.
168 | *
169 | * @param {String} str
170 | * @api public
171 | */
172 | set querystring(str) {
173 | const url = parse(this.req);
174 | if (url.search === `?${str}`)
175 | return;
176 | url.search = str;
177 | url.path = null;
178 | this.url = stringify(url);
179 | },
180 | /**
181 | * Get the search string. Same as the querystring
182 | * except it includes the leading ?.
183 | *
184 | * @return {String}
185 | * @api public
186 | */
187 | get search() {
188 | if (!this.querystring)
189 | return '';
190 | return `?${this.querystring}`;
191 | },
192 | /**
193 | * Set the search string. Same as
194 | * request.querystring= but included for ubiquity.
195 | *
196 | * @param {String} str
197 | * @api public
198 | */
199 | set search(str) {
200 | this.querystring = str;
201 | },
202 | /**
203 | * Parse the "Host" header field host
204 | * and support X-Forwarded-Host when a
205 | * proxy is enabled.
206 | *
207 | * @return {String} hostname:port
208 | * @api public
209 | */
210 | get host() {
211 | const proxy = this.app.proxy;
212 | let host = proxy && this.get('X-Forwarded-Host');
213 | if (!host) {
214 | if (this.req.httpVersionMajor >= 2)
215 | host = this.get(':authority');
216 | if (!host)
217 | host = this.get('Host');
218 | }
219 | if (!host)
220 | return '';
221 | return host.split(/\s*,\s*/, 1)[0];
222 | },
223 | /**
224 | * Parse the "Host" header field hostname
225 | * and support X-Forwarded-Host when a
226 | * proxy is enabled.
227 | *
228 | * @return {String} hostname
229 | * @api public
230 | */
231 | get hostname() {
232 | const host = this.host;
233 | if (!host)
234 | return '';
235 | if ('[' == host[0])
236 | return this.URL.hostname || ''; // IPv6
237 | return host.split(':', 1)[0];
238 | },
239 | /**
240 | * Get WHATWG parsed URL.
241 | * Lazily memoized.
242 | *
243 | * @return {URL|Object}
244 | * @api public
245 | */
246 | get URL() {
247 | /* istanbul ignore else */
248 | if (!this.memoizedURL) {
249 | const protocol = this.protocol;
250 | const host = this.host;
251 | const originalUrl = this.originalUrl || ''; // avoid undefined in template string
252 | try {
253 | this.memoizedURL = new URL(`${protocol}://${host}${originalUrl}`);
254 | }
255 | catch (err) {
256 | this.memoizedURL = Object.create(null);
257 | }
258 | }
259 | return this.memoizedURL;
260 | },
261 | /**
262 | * Check if the request is fresh, aka
263 | * Last-Modified and/or the ETag
264 | * still match.
265 | *
266 | * @return {Boolean}
267 | * @api public
268 | */
269 | get fresh() {
270 | const method = this.method;
271 | const s = this.ctx.status;
272 | // GET or HEAD for weak freshness validation only
273 | if ('GET' != method && 'HEAD' != method)
274 | return false;
275 | // 2xx or 304 as per rfc2616 14.26
276 | if ((s >= 200 && s < 300) || 304 == s) {
277 | return fresh(this.header, this.response.header);
278 | }
279 | return false;
280 | },
281 | /**
282 | * Check if the request is stale, aka
283 | * "Last-Modified" and / or the "ETag" for the
284 | * resource has changed.
285 | *
286 | * @return {Boolean}
287 | * @api public
288 | */
289 | get stale() {
290 | return !this.fresh;
291 | },
292 | /**
293 | * Check if the request is idempotent.
294 | *
295 | * @return {Boolean}
296 | * @api public
297 | */
298 | get idempotent() {
299 | const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];
300 | return !!~methods.indexOf(this.method);
301 | },
302 | /**
303 | * Return the request socket.
304 | *
305 | * @return {Connection}
306 | * @api public
307 | */
308 | get socket() {
309 | return this.req.socket;
310 | },
311 | /**
312 | * Get the charset when present or undefined.
313 | *
314 | * @return {String}
315 | * @api public
316 | */
317 | get charset() {
318 | try {
319 | const { parameters } = contentType.parse(this.req);
320 | return parameters.charset || '';
321 | }
322 | catch (e) {
323 | return '';
324 | }
325 | },
326 | /**
327 | * Return parsed Content-Length when present.
328 | *
329 | * @return {Number}
330 | * @api public
331 | */
332 | get length() {
333 | const len = this.get('Content-Length');
334 | if (len == '')
335 | return;
336 | return ~~len;
337 | },
338 | /**
339 | * Return the protocol string "http" or "https"
340 | * when requested with TLS. When the proxy setting
341 | * is enabled the "X-Forwarded-Proto" header
342 | * field will be trusted. If you're running behind
343 | * a reverse proxy that supplies https for you this
344 | * may be enabled.
345 | *
346 | * @return {String}
347 | * @api public
348 | */
349 | get protocol() {
350 | if (this.socket.encrypted)
351 | return 'https';
352 | if (!this.app.proxy)
353 | return 'http';
354 | const proto = this.get('X-Forwarded-Proto');
355 | return proto ? proto.split(/\s*,\s*/, 1)[0] : 'http';
356 | },
357 | /**
358 | * Short-hand for:
359 | *
360 | * this.protocol == 'https'
361 | *
362 | * @return {Boolean}
363 | * @api public
364 | */
365 | get secure() {
366 | return 'https' == this.protocol;
367 | },
368 | /**
369 | * When `app.proxy` is `true`, parse
370 | * the "X-Forwarded-For" ip address list.
371 | *
372 | * For example if the value were "client, proxy1, proxy2"
373 | * you would receive the array `["client", "proxy1", "proxy2"]`
374 | * where "proxy2" is the furthest down-stream.
375 | *
376 | * @return {Array}
377 | * @api public
378 | */
379 | get ips() {
380 | const proxy = this.app.proxy;
381 | const val = this.get('X-Forwarded-For');
382 | return proxy && val
383 | ? val.split(/\s*,\s*/)
384 | : [];
385 | },
386 | /**
387 | * Return request's remote address
388 | * When `app.proxy` is `true`, parse
389 | * the "X-Forwarded-For" ip address list and return the first one
390 | *
391 | * @return {String}
392 | * @api public
393 | */
394 | get ip() {
395 | if (!this[IP]) {
396 | this[IP] = this.ips[0] || this.socket.remoteAddress || '';
397 | }
398 | return this[IP];
399 | },
400 | set ip(_ip) {
401 | this[IP] = _ip;
402 | },
403 | /**
404 | * Return subdomains as an array.
405 | *
406 | * Subdomains are the dot-separated parts of the host before the main domain
407 | * of the app. By default, the domain of the app is assumed to be the last two
408 | * parts of the host. This can be changed by setting `app.subdomainOffset`.
409 | *
410 | * For example, if the domain is "tobi.ferrets.example.com":
411 | * If `app.subdomainOffset` is not set, this.subdomains is
412 | * `["ferrets", "tobi"]`.
413 | * If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`.
414 | *
415 | * @return {Array}
416 | * @api public
417 | */
418 | get subdomains() {
419 | const offset = this.app.subdomainOffset;
420 | const hostname = this.hostname;
421 | if (net.isIP(hostname))
422 | return [];
423 | return hostname
424 | .split('.')
425 | .reverse()
426 | .slice(offset);
427 | },
428 | /**
429 | * Get accept object.
430 | * Lazily memoized.
431 | *
432 | * @return {Object}
433 | * @api private
434 | */
435 | get accept() {
436 | return this._accept || (this._accept = accepts(this.req));
437 | },
438 | /**
439 | * Set accept object.
440 | *
441 | * @param {Object}
442 | * @api private
443 | */
444 | set accept(obj) {
445 | this._accept = obj;
446 | },
447 | /**
448 | * Check if the given `type(s)` is acceptable, returning
449 | * the best match when true, otherwise `false`, in which
450 | * case you should respond with 406 "Not Acceptable".
451 | *
452 | * The `type` value may be a single mime type string
453 | * such as "application/json", the extension name
454 | * such as "json" or an array `["json", "html", "text/plain"]`. When a list
455 | * or array is given the _best_ match, if any is returned.
456 | *
457 | * Examples:
458 | *
459 | * // Accept: text/html
460 | * this.accepts('html');
461 | * // => "html"
462 | *
463 | * // Accept: text/*, application/json
464 | * this.accepts('html');
465 | * // => "html"
466 | * this.accepts('text/html');
467 | * // => "text/html"
468 | * this.accepts('json', 'text');
469 | * // => "json"
470 | * this.accepts('application/json');
471 | * // => "application/json"
472 | *
473 | * // Accept: text/*, application/json
474 | * this.accepts('image/png');
475 | * this.accepts('png');
476 | * // => false
477 | *
478 | * // Accept: text/*;q=.5, application/json
479 | * this.accepts(['html', 'json']);
480 | * this.accepts('html', 'json');
481 | * // => "json"
482 | *
483 | * @param {String|Array} type(s)...
484 | * @return {String|Array|false}
485 | * @api public
486 | */
487 | accepts(...args) {
488 | return this.accept.types(...args);
489 | },
490 | /**
491 | * Return accepted encodings or best fit based on `encodings`.
492 | *
493 | * Given `Accept-Encoding: gzip, deflate`
494 | * an array sorted by quality is returned:
495 | *
496 | * ['gzip', 'deflate']
497 | *
498 | * @param {String|Array} encoding(s)...
499 | * @return {String|Array}
500 | * @api public
501 | */
502 | acceptsEncodings(...args) {
503 | return this.accept.encodings(...args);
504 | },
505 | /**
506 | * Return accepted charsets or best fit based on `charsets`.
507 | *
508 | * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
509 | * an array sorted by quality is returned:
510 | *
511 | * ['utf-8', 'utf-7', 'iso-8859-1']
512 | *
513 | * @param {String|Array} charset(s)...
514 | * @return {String|Array}
515 | * @api public
516 | */
517 | acceptsCharsets(...args) {
518 | return this.accept.charsets(...args);
519 | },
520 | /**
521 | * Return accepted languages or best fit based on `langs`.
522 | *
523 | * Given `Accept-Language: en;q=0.8, es, pt`
524 | * an array sorted by quality is returned:
525 | *
526 | * ['es', 'pt', 'en']
527 | *
528 | * @param {String|Array} lang(s)...
529 | * @return {Array|String}
530 | * @api public
531 | */
532 | acceptsLanguages(...args) {
533 | return this.accept.languages(...args);
534 | },
535 | /**
536 | * Check if the incoming request contains the "Content-Type"
537 | * header field, and it contains any of the give mime `type`s.
538 | * If there is no request body, `null` is returned.
539 | * If there is no content type, `false` is returned.
540 | * Otherwise, it returns the first `type` that matches.
541 | *
542 | * Examples:
543 | *
544 | * // With Content-Type: text/html; charset=utf-8
545 | * this.is('html'); // => 'html'
546 | * this.is('text/html'); // => 'text/html'
547 | * this.is('text/*', 'application/json'); // => 'text/html'
548 | *
549 | * // When Content-Type is application/json
550 | * this.is('json', 'urlencoded'); // => 'json'
551 | * this.is('application/json'); // => 'application/json'
552 | * this.is('html', 'application/*'); // => 'application/json'
553 | *
554 | * this.is('html'); // => false
555 | *
556 | * @param {String|Array} types...
557 | * @return {String|false|null}
558 | * @api public
559 | */
560 | is(types) {
561 | if (!types)
562 | return typeis(this.req);
563 | if (!Array.isArray(types))
564 | types = [].slice.call(arguments);
565 | return typeis(this.req, types);
566 | },
567 | /**
568 | * Return the request mime type void of
569 | * parameters such as "charset".
570 | *
571 | * @return {String}
572 | * @api public
573 | */
574 | get type() {
575 | const type = this.get('Content-Type');
576 | if (!type)
577 | return '';
578 | return type.split(';')[0];
579 | },
580 | /**
581 | * Return request header.
582 | *
583 | * The `Referrer` header field is special-cased,
584 | * both `Referrer` and `Referer` are interchangeable.
585 | *
586 | * Examples:
587 | *
588 | * this.get('Content-Type');
589 | * // => "text/plain"
590 | *
591 | * this.get('content-type');
592 | * // => "text/plain"
593 | *
594 | * this.get('Something');
595 | * // => ''
596 | *
597 | * @param {String} field
598 | * @return {String}
599 | * @api public
600 | */
601 | get(field) {
602 | const req = this.req;
603 | switch (field = field.toLowerCase()) {
604 | case 'referer':
605 | case 'referrer':
606 | return req.headers.referrer || req.headers.referer || '';
607 | default:
608 | return req.headers[field] || '';
609 | }
610 | },
611 | /**
612 | * Inspect implementation.
613 | *
614 | * @return {Object}
615 | * @api public
616 | */
617 | inspect() {
618 | if (!this.req)
619 | return;
620 | return this.toJSON();
621 | },
622 | /**
623 | * Return JSON representation.
624 | *
625 | * @return {Object}
626 | * @api public
627 | */
628 | toJSON() {
629 | return only(this, [
630 | 'method',
631 | 'url',
632 | 'header'
633 | ]);
634 | }
635 | };
636 | /**
637 | * Custom inspection implementation for newer Node.js versions.
638 | *
639 | * @return {Object}
640 | * @api public
641 | */
642 | /* istanbul ignore else */
643 | if (util.inspect.custom) {
644 | module.exports[util.inspect.custom] = module.exports.inspect;
645 | }
646 |
--------------------------------------------------------------------------------
/lib/request.ts:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | /**
5 | * Module dependencies.
6 | */
7 |
8 | import net = require('net');
9 | import accepts = require('accepts');
10 | import contentType = require('content-type');
11 | import parse = require('parseurl');
12 | import qs = require('querystring');
13 | import typeis = require('type-is');
14 | import fresh = require('fresh');
15 | import only = require('only');
16 | import util = require('util');
17 | const URL = require('url').URL;
18 | const stringify = require('url').format;
19 |
20 | const IP = Symbol('context#ip');
21 |
22 | /**
23 | * Prototype.
24 | */
25 |
26 | module.exports = {
27 |
28 | /**
29 | * Return request header.
30 | *
31 | * @return {Object}
32 | * @api public
33 | */
34 |
35 | get header(): object {
36 | return this.req.headers;
37 | },
38 |
39 | /**
40 | * Set request header.
41 | *
42 | * @api public
43 | */
44 |
45 | set header(val) {
46 | this.req.headers = val;
47 | },
48 |
49 | /**
50 | * Return request header, alias as request.header
51 | *
52 | * @return {Object}
53 | * @api public
54 | */
55 |
56 | get headers(): object {
57 | return this.req.headers;
58 | },
59 |
60 | /**
61 | * Set request header, alias as request.header
62 | *
63 | * @api public
64 | */
65 |
66 | set headers(val) {
67 | this.req.headers = val;
68 | },
69 |
70 | /**
71 | * Get request URL.
72 | *
73 | * @return {String}
74 | * @api public
75 | */
76 |
77 | get url(): string {
78 | return this.req.url;
79 | },
80 |
81 | /**
82 | * Set request URL.
83 | *
84 | * @api public
85 | */
86 |
87 | set url(val) {
88 | this.req.url = val;
89 | },
90 |
91 | /**
92 | * Get origin of URL.
93 | *
94 | * @return {String}
95 | * @api public
96 | */
97 |
98 | get origin(): string {
99 | return `${this.protocol}://${this.host}`;
100 | },
101 |
102 | /**
103 | * Get full request URL.
104 | *
105 | * @return {String}
106 | * @api public
107 | */
108 |
109 | get href(): string {
110 | // support: `GET http://example.com/foo`
111 | if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl;
112 | return this.origin + this.originalUrl;
113 | },
114 |
115 | /**
116 | * Get request method.
117 | *
118 | * @return {String}
119 | * @api public
120 | */
121 |
122 | get method(): string {
123 | return this.req.method;
124 | },
125 |
126 | /**
127 | * Set request method.
128 | *
129 | * @param {String} val
130 | * @api public
131 | */
132 |
133 | set method(val: string) {
134 | this.req.method = val;
135 | },
136 |
137 | /**
138 | * Get request pathname.
139 | *
140 | * @return {String}
141 | * @api public
142 | */
143 |
144 | get path(): string {
145 | return parse(this.req).pathname;
146 | },
147 |
148 | /**
149 | * Set pathname, retaining the query-string when present.
150 | *
151 | * @param {String} path
152 | * @api public
153 | */
154 |
155 | set path(path: string) {
156 | const url = parse(this.req);
157 | if (url.pathname === path) return;
158 |
159 | url.pathname = path;
160 | url.path = null;
161 |
162 | this.url = stringify(url);
163 | },
164 |
165 | /**
166 | * Get parsed query-string.
167 | *
168 | * @return {Object}
169 | * @api public
170 | */
171 |
172 | get query(): any {
173 | const str = this.querystring;
174 | const c = this._querycache = this._querycache || {};
175 | return c[str] || (c[str] = qs.parse(str));
176 | },
177 |
178 | /**
179 | * Set query-string as an object.
180 | *
181 | * @param {Object} obj
182 | * @api public
183 | */
184 |
185 | set query(obj: any) {
186 | this.querystring = qs.stringify(obj);
187 | },
188 |
189 | /**
190 | * Get query string.
191 | *
192 | * @return {String}
193 | * @api public
194 | */
195 |
196 | get querystring(): any {
197 | if (!this.req) return '';
198 | return parse(this.req).query || '';
199 | },
200 |
201 | /**
202 | * Set querystring.
203 | *
204 | * @param {String} str
205 | * @api public
206 | */
207 |
208 | set querystring(str: any) {
209 | const url = parse(this.req);
210 | if (url.search === `?${str}`) return;
211 |
212 | url.search = str;
213 | url.path = null;
214 |
215 | this.url = stringify(url);
216 | },
217 |
218 | /**
219 | * Get the search string. Same as the querystring
220 | * except it includes the leading ?.
221 | *
222 | * @return {String}
223 | * @api public
224 | */
225 |
226 | get search(): string {
227 | if (!this.querystring) return '';
228 | return `?${this.querystring}`;
229 | },
230 |
231 | /**
232 | * Set the search string. Same as
233 | * request.querystring= but included for ubiquity.
234 | *
235 | * @param {String} str
236 | * @api public
237 | */
238 |
239 | set search(str: string) {
240 | this.querystring = str;
241 | },
242 |
243 | /**
244 | * Parse the "Host" header field host
245 | * and support X-Forwarded-Host when a
246 | * proxy is enabled.
247 | *
248 | * @return {String} hostname:port
249 | * @api public
250 | */
251 |
252 | get host(): string {
253 | const proxy = this.app.proxy;
254 | let host = proxy && this.get('X-Forwarded-Host');
255 | if (!host) {
256 | if (this.req.httpVersionMajor >= 2) host = this.get(':authority');
257 | if (!host) host = this.get('Host');
258 | }
259 | if (!host) return '';
260 | return host.split(/\s*,\s*/, 1)[0];
261 | },
262 |
263 | /**
264 | * Parse the "Host" header field hostname
265 | * and support X-Forwarded-Host when a
266 | * proxy is enabled.
267 | *
268 | * @return {String} hostname
269 | * @api public
270 | */
271 |
272 | get hostname(): string {
273 | const host = this.host;
274 | if (!host) return '';
275 | if ('[' == host[0]) return this.URL.hostname || ''; // IPv6
276 | return host.split(':', 1)[0];
277 | },
278 |
279 | /**
280 | * Get WHATWG parsed URL.
281 | * Lazily memoized.
282 | *
283 | * @return {URL|Object}
284 | * @api public
285 | */
286 |
287 | get URL(): URL | object {
288 | /* istanbul ignore else */
289 | if (!this.memoizedURL) {
290 | const protocol = this.protocol;
291 | const host = this.host;
292 | const originalUrl = this.originalUrl || ''; // avoid undefined in template string
293 | try {
294 | this.memoizedURL = new URL(`${protocol}://${host}${originalUrl}`);
295 | } catch (err) {
296 | this.memoizedURL = Object.create(null);
297 | }
298 | }
299 | return this.memoizedURL;
300 | },
301 |
302 | /**
303 | * Check if the request is fresh, aka
304 | * Last-Modified and/or the ETag
305 | * still match.
306 | *
307 | * @return {Boolean}
308 | * @api public
309 | */
310 |
311 | get fresh(): boolean {
312 | const method = this.method;
313 | const s = this.ctx.status;
314 |
315 | // GET or HEAD for weak freshness validation only
316 | if ('GET' != method && 'HEAD' != method) return false;
317 |
318 | // 2xx or 304 as per rfc2616 14.26
319 | if ((s >= 200 && s < 300) || 304 == s) {
320 | return fresh(this.header, this.response.header);
321 | }
322 |
323 | return false;
324 | },
325 |
326 | /**
327 | * Check if the request is stale, aka
328 | * "Last-Modified" and / or the "ETag" for the
329 | * resource has changed.
330 | *
331 | * @return {Boolean}
332 | * @api public
333 | */
334 |
335 | get stale(): boolean {
336 | return !this.fresh;
337 | },
338 |
339 | /**
340 | * Check if the request is idempotent.
341 | *
342 | * @return {Boolean}
343 | * @api public
344 | */
345 |
346 | get idempotent(): boolean {
347 | const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];
348 | return !!~methods.indexOf(this.method);
349 | },
350 |
351 | /**
352 | * Return the request socket.
353 | *
354 | * @return {Connection}
355 | * @api public
356 | */
357 |
358 | get socket(): object {
359 | return this.req.socket;
360 | },
361 |
362 | /**
363 | * Get the charset when present or undefined.
364 | *
365 | * @return {String}
366 | * @api public
367 | */
368 |
369 | get charset(): string {
370 | try {
371 | const { parameters } = contentType.parse(this.req);
372 | return parameters.charset || '';
373 | } catch (e) {
374 | return '';
375 | }
376 | },
377 |
378 | /**
379 | * Return parsed Content-Length when present.
380 | *
381 | * @return {Number}
382 | * @api public
383 | */
384 |
385 | get length(): number {
386 | const len = this.get('Content-Length');
387 | if (len == '') return;
388 | return ~~len;
389 | },
390 |
391 | /**
392 | * Return the protocol string "http" or "https"
393 | * when requested with TLS. When the proxy setting
394 | * is enabled the "X-Forwarded-Proto" header
395 | * field will be trusted. If you're running behind
396 | * a reverse proxy that supplies https for you this
397 | * may be enabled.
398 | *
399 | * @return {String}
400 | * @api public
401 | */
402 |
403 | get protocol(): string {
404 | if (this.socket.encrypted) return 'https';
405 | if (!this.app.proxy) return 'http';
406 | const proto = this.get('X-Forwarded-Proto');
407 | return proto ? proto.split(/\s*,\s*/, 1)[0] : 'http';
408 | },
409 |
410 | /**
411 | * Short-hand for:
412 | *
413 | * this.protocol == 'https'
414 | *
415 | * @return {Boolean}
416 | * @api public
417 | */
418 |
419 | get secure(): boolean {
420 | return 'https' == this.protocol;
421 | },
422 |
423 | /**
424 | * When `app.proxy` is `true`, parse
425 | * the "X-Forwarded-For" ip address list.
426 | *
427 | * For example if the value were "client, proxy1, proxy2"
428 | * you would receive the array `["client", "proxy1", "proxy2"]`
429 | * where "proxy2" is the furthest down-stream.
430 | *
431 | * @return {Array}
432 | * @api public
433 | */
434 |
435 | get ips(): Array {
436 | const proxy = this.app.proxy;
437 | const val = this.get('X-Forwarded-For');
438 | return proxy && val
439 | ? val.split(/\s*,\s*/)
440 | : [];
441 | },
442 |
443 | /**
444 | * Return request's remote address
445 | * When `app.proxy` is `true`, parse
446 | * the "X-Forwarded-For" ip address list and return the first one
447 | *
448 | * @return {String}
449 | * @api public
450 | */
451 |
452 | get ip(): string {
453 | if (!this[IP]) {
454 | this[IP] = this.ips[0] || this.socket.remoteAddress || '';
455 | }
456 | return this[IP];
457 | },
458 |
459 | set ip(_ip) {
460 | this[IP] = _ip;
461 | },
462 |
463 | /**
464 | * Return subdomains as an array.
465 | *
466 | * Subdomains are the dot-separated parts of the host before the main domain
467 | * of the app. By default, the domain of the app is assumed to be the last two
468 | * parts of the host. This can be changed by setting `app.subdomainOffset`.
469 | *
470 | * For example, if the domain is "tobi.ferrets.example.com":
471 | * If `app.subdomainOffset` is not set, this.subdomains is
472 | * `["ferrets", "tobi"]`.
473 | * If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`.
474 | *
475 | * @return {Array}
476 | * @api public
477 | */
478 |
479 | get subdomains(): Array {
480 | const offset = this.app.subdomainOffset;
481 | const hostname = this.hostname;
482 | if (net.isIP(hostname)) return [];
483 | return hostname
484 | .split('.')
485 | .reverse()
486 | .slice(offset);
487 | },
488 |
489 | /**
490 | * Get accept object.
491 | * Lazily memoized.
492 | *
493 | * @return {Object}
494 | * @api private
495 | */
496 | get accept(): object {
497 | return this._accept || (this._accept = accepts(this.req));
498 | },
499 |
500 | /**
501 | * Set accept object.
502 | *
503 | * @param {Object}
504 | * @api private
505 | */
506 | set accept(obj: object) {
507 | this._accept = obj;
508 | },
509 |
510 | /**
511 | * Check if the given `type(s)` is acceptable, returning
512 | * the best match when true, otherwise `false`, in which
513 | * case you should respond with 406 "Not Acceptable".
514 | *
515 | * The `type` value may be a single mime type string
516 | * such as "application/json", the extension name
517 | * such as "json" or an array `["json", "html", "text/plain"]`. When a list
518 | * or array is given the _best_ match, if any is returned.
519 | *
520 | * Examples:
521 | *
522 | * // Accept: text/html
523 | * this.accepts('html');
524 | * // => "html"
525 | *
526 | * // Accept: text/*, application/json
527 | * this.accepts('html');
528 | * // => "html"
529 | * this.accepts('text/html');
530 | * // => "text/html"
531 | * this.accepts('json', 'text');
532 | * // => "json"
533 | * this.accepts('application/json');
534 | * // => "application/json"
535 | *
536 | * // Accept: text/*, application/json
537 | * this.accepts('image/png');
538 | * this.accepts('png');
539 | * // => false
540 | *
541 | * // Accept: text/*;q=.5, application/json
542 | * this.accepts(['html', 'json']);
543 | * this.accepts('html', 'json');
544 | * // => "json"
545 | *
546 | * @param {String|Array} type(s)...
547 | * @return {String|Array|false}
548 | * @api public
549 | */
550 |
551 | accepts(...args: any[]): string | Array | false {
552 | return this.accept.types(...args);
553 | },
554 |
555 | /**
556 | * Return accepted encodings or best fit based on `encodings`.
557 | *
558 | * Given `Accept-Encoding: gzip, deflate`
559 | * an array sorted by quality is returned:
560 | *
561 | * ['gzip', 'deflate']
562 | *
563 | * @param {String|Array} encoding(s)...
564 | * @return {String|Array}
565 | * @api public
566 | */
567 |
568 | acceptsEncodings(...args: any[]): string | Array {
569 | return this.accept.encodings(...args);
570 | },
571 |
572 | /**
573 | * Return accepted charsets or best fit based on `charsets`.
574 | *
575 | * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
576 | * an array sorted by quality is returned:
577 | *
578 | * ['utf-8', 'utf-7', 'iso-8859-1']
579 | *
580 | * @param {String|Array} charset(s)...
581 | * @return {String|Array}
582 | * @api public
583 | */
584 |
585 | acceptsCharsets(...args: any[]): string | Array {
586 | return this.accept.charsets(...args);
587 | },
588 |
589 | /**
590 | * Return accepted languages or best fit based on `langs`.
591 | *
592 | * Given `Accept-Language: en;q=0.8, es, pt`
593 | * an array sorted by quality is returned:
594 | *
595 | * ['es', 'pt', 'en']
596 | *
597 | * @param {String|Array} lang(s)...
598 | * @return {Array|String}
599 | * @api public
600 | */
601 |
602 | acceptsLanguages(...args: any[]): Array | string {
603 | return this.accept.languages(...args);
604 | },
605 |
606 | /**
607 | * Check if the incoming request contains the "Content-Type"
608 | * header field, and it contains any of the give mime `type`s.
609 | * If there is no request body, `null` is returned.
610 | * If there is no content type, `false` is returned.
611 | * Otherwise, it returns the first `type` that matches.
612 | *
613 | * Examples:
614 | *
615 | * // With Content-Type: text/html; charset=utf-8
616 | * this.is('html'); // => 'html'
617 | * this.is('text/html'); // => 'text/html'
618 | * this.is('text/*', 'application/json'); // => 'text/html'
619 | *
620 | * // When Content-Type is application/json
621 | * this.is('json', 'urlencoded'); // => 'json'
622 | * this.is('application/json'); // => 'application/json'
623 | * this.is('html', 'application/*'); // => 'application/json'
624 | *
625 | * this.is('html'); // => false
626 | *
627 | * @param {String|Array} types...
628 | * @return {String|false|null}
629 | * @api public
630 | */
631 |
632 | is(types: string[]): string | false | null {
633 | if (!types) return typeis(this.req);
634 | if (!Array.isArray(types)) types = [].slice.call(arguments);
635 | return typeis(this.req, types);
636 | },
637 |
638 | /**
639 | * Return the request mime type void of
640 | * parameters such as "charset".
641 | *
642 | * @return {String}
643 | * @api public
644 | */
645 |
646 | get type(): string {
647 | const type = this.get('Content-Type');
648 | if (!type) return '';
649 | return type.split(';')[0];
650 | },
651 |
652 | /**
653 | * Return request header.
654 | *
655 | * The `Referrer` header field is special-cased,
656 | * both `Referrer` and `Referer` are interchangeable.
657 | *
658 | * Examples:
659 | *
660 | * this.get('Content-Type');
661 | * // => "text/plain"
662 | *
663 | * this.get('content-type');
664 | * // => "text/plain"
665 | *
666 | * this.get('Something');
667 | * // => ''
668 | *
669 | * @param {String} field
670 | * @return {String}
671 | * @api public
672 | */
673 |
674 | get(field: string): string {
675 | const req = this.req;
676 | switch (field = field.toLowerCase()) {
677 | case 'referer':
678 | case 'referrer':
679 | return req.headers.referrer || req.headers.referer || '';
680 | default:
681 | return req.headers[field] || '';
682 | }
683 | },
684 |
685 | /**
686 | * Inspect implementation.
687 | *
688 | * @return {Object}
689 | * @api public
690 | */
691 |
692 | inspect(): object {
693 | if (!this.req) return;
694 | return this.toJSON();
695 | },
696 |
697 | /**
698 | * Return JSON representation.
699 | *
700 | * @return {Object}
701 | * @api public
702 | */
703 |
704 | toJSON(): object {
705 | return only(this, [
706 | 'method',
707 | 'url',
708 | 'header'
709 | ]);
710 | }
711 | };
712 |
713 | /**
714 | * Custom inspection implementation for newer Node.js versions.
715 | *
716 | * @return {Object}
717 | * @api public
718 | */
719 |
720 | /* istanbul ignore else */
721 | if (util.inspect.custom) {
722 | module.exports[util.inspect.custom] = module.exports.inspect;
723 | }
724 |
--------------------------------------------------------------------------------
/lib/response.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | /**
4 | * Module dependencies.
5 | */
6 | const contentDisposition = require("content-disposition");
7 | const ensureErrorHandler = require("error-inject");
8 | const getType = require("cache-content-type");
9 | const onFinish = require("on-finished");
10 | const isJSON = require("koa-is-json");
11 | const escape = require("escape-html");
12 | const statuses = require("statuses");
13 | const destroy = require("destroy");
14 | const assert = require("assert");
15 | const vary = require("vary");
16 | const only = require("only");
17 | const util = require("util");
18 | const extname = require('path').extname;
19 | const typeis = require('type-is').is;
20 | /**
21 | * Prototype.
22 | */
23 | exports = {
24 | /**
25 | * Return the request socket.
26 | *
27 | * @return {Connection}
28 | * @api public
29 | */
30 | get socket() {
31 | return this.res.socket;
32 | },
33 | /**
34 | * Return response header.
35 | *
36 | * @return {Object}
37 | * @api public
38 | */
39 | get header() {
40 | const { res } = this;
41 | return typeof res.getHeaders === 'function'
42 | ? res.getHeaders()
43 | : res._headers || {}; // Node < 7.7
44 | },
45 | /**
46 | * Return response header, alias as response.header
47 | *
48 | * @return {Object}
49 | * @api public
50 | */
51 | get headers() {
52 | return this.header;
53 | },
54 | /**
55 | * Get response status code.
56 | *
57 | * @return {Number}
58 | * @api public
59 | */
60 | get status() {
61 | return this.res.statusCode;
62 | },
63 | /**
64 | * Set response status code.
65 | *
66 | * @param {Number} code
67 | * @api public
68 | */
69 | set status(code) {
70 | if (this.headerSent)
71 | return;
72 | assert(Number.isInteger(code), 'status code must be a number');
73 | assert(code >= 100 && code <= 999, `invalid status code: ${code}`);
74 | this._explicitStatus = true;
75 | this.res.statusCode = code;
76 | if (this.req.httpVersionMajor < 2)
77 | this.res.statusMessage = statuses[code];
78 | if (this.body && statuses.empty[code])
79 | this.body = null;
80 | },
81 | /**
82 | * Get response status message
83 | *
84 | * @return {String}
85 | * @api public
86 | */
87 | get message() {
88 | return this.res.statusMessage || statuses[this.status];
89 | },
90 | /**
91 | * Set response status message
92 | *
93 | * @param {String} msg
94 | * @api public
95 | */
96 | set message(msg) {
97 | this.res.statusMessage = msg;
98 | },
99 | /**
100 | * Get response body.
101 | *
102 | * @return {Mixed}
103 | * @api public
104 | */
105 | get body() {
106 | return this._body;
107 | },
108 | /**
109 | * Set response body.
110 | *
111 | * @param {String|Buffer|Object|Stream} val
112 | * @api public
113 | */
114 | set body(val) {
115 | const original = this._body;
116 | this._body = val;
117 | // no content
118 | if (null == val) {
119 | if (!statuses.empty[this.status])
120 | this.status = 204;
121 | this.remove('Content-Type');
122 | this.remove('Content-Length');
123 | this.remove('Transfer-Encoding');
124 | return;
125 | }
126 | // set the status
127 | if (!this._explicitStatus)
128 | this.status = 200;
129 | // set the content-type only if not yet set
130 | const setType = !this.header['content-type'];
131 | // string
132 | if ('string' == typeof val) {
133 | if (setType)
134 | this.type = /^\s* this.ctx.onerror(err));
149 | // overwriting
150 | if (null != original && original != val)
151 | this.remove('Content-Length');
152 | if (setType)
153 | this.type = 'bin';
154 | return;
155 | }
156 | // json
157 | this.remove('Content-Length');
158 | this.type = 'json';
159 | },
160 | /**
161 | * Set Content-Length field to `n`.
162 | *
163 | * @param {Number} n
164 | * @api public
165 | */
166 | set length(n) {
167 | this.set('Content-Length', n);
168 | },
169 | /**
170 | * Return parsed response Content-Length when present.
171 | *
172 | * @return {Number}
173 | * @api public
174 | */
175 | get length() {
176 | const len = this.header['content-length'];
177 | const body = this.body;
178 | if (null == len) {
179 | if (!body)
180 | return;
181 | if ('string' == typeof body)
182 | return Buffer.byteLength(body);
183 | if (Buffer.isBuffer(body))
184 | return body.length;
185 | if (isJSON(body))
186 | return Buffer.byteLength(JSON.stringify(body));
187 | return;
188 | }
189 | return Math.trunc(len) || 0;
190 | },
191 | /**
192 | * Check if a header has been written to the socket.
193 | *
194 | * @return {Boolean}
195 | * @api public
196 | */
197 | get headerSent() {
198 | return this.res.headersSent;
199 | },
200 | /**
201 | * Vary on `field`.
202 | *
203 | * @param {String} field
204 | * @api public
205 | */
206 | vary(field) {
207 | if (this.headerSent)
208 | return;
209 | vary(this.res, field);
210 | },
211 | /**
212 | * Perform a 302 redirect to `url`.
213 | *
214 | * The string "back" is special-cased
215 | * to provide Referrer support, when Referrer
216 | * is not present `alt` or "/" is used.
217 | *
218 | * Examples:
219 | *
220 | * this.redirect('back');
221 | * this.redirect('back', '/index.html');
222 | * this.redirect('/login');
223 | * this.redirect('http://google.com');
224 | *
225 | * @param {String} url
226 | * @param {String} [alt]
227 | * @api public
228 | */
229 | redirect(url, alt) {
230 | // location
231 | if ('back' == url)
232 | url = this.ctx.get('Referrer') || alt || '/';
233 | this.set('Location', url);
234 | // status
235 | if (!statuses.redirect[this.status])
236 | this.status = 302;
237 | // html
238 | if (this.ctx.accepts('html')) {
239 | url = escape(url);
240 | this.type = 'text/html; charset=utf-8';
241 | this.body = `Redirecting to ${url}.`;
242 | return;
243 | }
244 | // text
245 | this.type = 'text/plain; charset=utf-8';
246 | this.body = `Redirecting to ${url}.`;
247 | },
248 | /**
249 | * Set Content-Disposition header to "attachment" with optional `filename`.
250 | *
251 | * @param {String} filename
252 | * @api public
253 | */
254 | attachment(filename, options) {
255 | if (filename)
256 | this.type = extname(filename);
257 | this.set('Content-Disposition', contentDisposition(filename, options));
258 | },
259 | /**
260 | * Set Content-Type response header with `type` through `mime.lookup()`
261 | * when it does not contain a charset.
262 | *
263 | * Examples:
264 | *
265 | * this.type = '.html';
266 | * this.type = 'html';
267 | * this.type = 'json';
268 | * this.type = 'application/json';
269 | * this.type = 'png';
270 | *
271 | * @param {String} type
272 | * @api public
273 | */
274 | set type(type) {
275 | type = getType(type);
276 | if (type) {
277 | this.set('Content-Type', type);
278 | }
279 | else {
280 | this.remove('Content-Type');
281 | }
282 | },
283 | /**
284 | * Set the Last-Modified date using a string or a Date.
285 | *
286 | * this.response.lastModified = new Date();
287 | * this.response.lastModified = '2013-09-13';
288 | *
289 | * @param {String|Date} type
290 | * @api public
291 | */
292 | set lastModified(val) {
293 | if ('string' == typeof val)
294 | val = new Date(val);
295 | this.set('Last-Modified', val.toUTCString());
296 | },
297 | /**
298 | * Get the Last-Modified date in Date form, if it exists.
299 | *
300 | * @return {Date}
301 | * @api public
302 | */
303 | get lastModified() {
304 | const date = this.get('last-modified');
305 | if (date)
306 | return new Date(date);
307 | },
308 | /**
309 | * Set the ETag of a response.
310 | * This will normalize the quotes if necessary.
311 | *
312 | * this.response.etag = 'md5hashsum';
313 | * this.response.etag = '"md5hashsum"';
314 | * this.response.etag = 'W/"123456789"';
315 | *
316 | * @param {String} etag
317 | * @api public
318 | */
319 | set etag(val) {
320 | if (!/^(W\/)?"/.test(val))
321 | val = `"${val}"`;
322 | this.set('ETag', val);
323 | },
324 | /**
325 | * Get the ETag of a response.
326 | *
327 | * @return {String}
328 | * @api public
329 | */
330 | get etag() {
331 | return this.get('ETag');
332 | },
333 | /**
334 | * Return the response mime type void of
335 | * parameters such as "charset".
336 | *
337 | * @return {String}
338 | * @api public
339 | */
340 | get type() {
341 | const type = this.get('Content-Type');
342 | if (!type)
343 | return '';
344 | return type.split(';', 1)[0];
345 | },
346 | /**
347 | * Check whether the response is one of the listed types.
348 | * Pretty much the same as `this.request.is()`.
349 | *
350 | * @param {String|Array} types...
351 | * @return {String|false}
352 | * @api public
353 | */
354 | is(types) {
355 | const type = this.type;
356 | if (!types)
357 | return type || false;
358 | if (!Array.isArray(types))
359 | types = [].slice.call(arguments);
360 | return typeis(type, types);
361 | },
362 | /**
363 | * Return response header.
364 | *
365 | * Examples:
366 | *
367 | * this.get('Content-Type');
368 | * // => "text/plain"
369 | *
370 | * this.get('content-type');
371 | * // => "text/plain"
372 | *
373 | * @param {String} field
374 | * @return {String}
375 | * @api public
376 | */
377 | get(field) {
378 | return this.header[field.toLowerCase()] || '';
379 | },
380 | /**
381 | * Set header `field` to `val`, or pass
382 | * an object of header fields.
383 | *
384 | * Examples:
385 | *
386 | * this.set('Foo', ['bar', 'baz']);
387 | * this.set('Accept', 'application/json');
388 | * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
389 | *
390 | * @param {String|Object|Array} field
391 | * @param {String} val
392 | * @api public
393 | */
394 | set(field, val) {
395 | if (this.headerSent)
396 | return;
397 | if (2 == arguments.length) {
398 | if (Array.isArray(val))
399 | val = val.map(v => typeof v === 'string' ? v : String(v));
400 | else if (typeof val !== 'string')
401 | val = String(val);
402 | this.res.setHeader(field, val);
403 | }
404 | else {
405 | for (const key in field) {
406 | this.set(key, field[key]);
407 | }
408 | }
409 | },
410 | /**
411 | * Append additional header `field` with value `val`.
412 | *
413 | * Examples:
414 | *
415 | * ```
416 | * this.append('Link', ['', '']);
417 | * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
418 | * this.append('Warning', '199 Miscellaneous warning');
419 | * ```
420 | *
421 | * @param {String} field
422 | * @param {String|Array} val
423 | * @api public
424 | */
425 | append(field, val) {
426 | const prev = this.get(field);
427 | if (prev) {
428 | val = Array.isArray(prev)
429 | ? prev.concat(val)
430 | : [prev].concat(val);
431 | }
432 | return this.set(field, val);
433 | },
434 | /**
435 | * Remove header `field`.
436 | *
437 | * @param {String} name
438 | * @api public
439 | */
440 | remove(field) {
441 | if (this.headerSent)
442 | return;
443 | this.res.removeHeader(field);
444 | },
445 | /**
446 | * Checks if the request is writable.
447 | * Tests for the existence of the socket
448 | * as node sometimes does not set it.
449 | *
450 | * @return {Boolean}
451 | * @api private
452 | */
453 | get writable() {
454 | // can't write any more after response finished
455 | if (this.res.finished)
456 | return false;
457 | const socket = this.res.socket;
458 | // There are already pending outgoing res, but still writable
459 | // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
460 | if (!socket)
461 | return true;
462 | return socket.writable;
463 | },
464 | /**
465 | * Inspect implementation.
466 | *
467 | * @return {Object}
468 | * @api public
469 | */
470 | inspect() {
471 | if (!this.res)
472 | return;
473 | const o = this.toJSON();
474 | o.body = this.body;
475 | return o;
476 | },
477 | /**
478 | * Return JSON representation.
479 | *
480 | * @return {Object}
481 | * @api public
482 | */
483 | toJSON() {
484 | return only(this, [
485 | 'status',
486 | 'message',
487 | 'header'
488 | ]);
489 | },
490 | /**
491 | * Flush any set headers, and begin the body
492 | */
493 | flushHeaders() {
494 | this.res.flushHeaders();
495 | }
496 | };
497 | /**
498 | * Custom inspection implementation for newer Node.js versions.
499 | *
500 | * @return {Object}
501 | * @api public
502 | */
503 | if (util.inspect.custom) {
504 | exports[util.inspect.custom] = exports.inspect;
505 | }
506 |
--------------------------------------------------------------------------------
/lib/response.ts:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | /**
5 | * Module dependencies.
6 | */
7 |
8 | import contentDisposition = require('content-disposition');
9 | import ensureErrorHandler = require('error-inject');
10 | import getType = require('cache-content-type');
11 | import onFinish = require('on-finished');
12 | import isJSON = require('koa-is-json');
13 | import escape = require('escape-html');
14 | import statuses = require('statuses');
15 | import destroy = require('destroy');
16 | import assert = require('assert');
17 | import vary = require('vary');
18 | import only = require('only');
19 | import util = require('util');
20 | const extname = require('path').extname;
21 | const typeis = require('type-is').is;
22 |
23 | /**
24 | * Prototype.
25 | */
26 |
27 | exports = {
28 |
29 | /**
30 | * Return the request socket.
31 | *
32 | * @return {Connection}
33 | * @api public
34 | */
35 |
36 | get socket(): object {
37 | return this.res.socket;
38 | },
39 |
40 | /**
41 | * Return response header.
42 | *
43 | * @return {Object}
44 | * @api public
45 | */
46 |
47 | get header(): object {
48 | const { res } = this;
49 | return typeof res.getHeaders === 'function'
50 | ? res.getHeaders()
51 | : res._headers || {}; // Node < 7.7
52 | },
53 |
54 | /**
55 | * Return response header, alias as response.header
56 | *
57 | * @return {Object}
58 | * @api public
59 | */
60 |
61 | get headers(): object {
62 | return this.header;
63 | },
64 |
65 | /**
66 | * Get response status code.
67 | *
68 | * @return {Number}
69 | * @api public
70 | */
71 |
72 | get status(): number {
73 | return this.res.statusCode;
74 | },
75 |
76 | /**
77 | * Set response status code.
78 | *
79 | * @param {Number} code
80 | * @api public
81 | */
82 |
83 | set status(code: number) {
84 | if (this.headerSent) return;
85 |
86 | assert(Number.isInteger(code), 'status code must be a number');
87 | assert(code >= 100 && code <= 999, `invalid status code: ${code}`);
88 | this._explicitStatus = true;
89 | this.res.statusCode = code;
90 | if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code];
91 | if (this.body && statuses.empty[code]) this.body = null;
92 | },
93 |
94 | /**
95 | * Get response status message
96 | *
97 | * @return {String}
98 | * @api public
99 | */
100 |
101 | get message(): string {
102 | return this.res.statusMessage || statuses[this.status];
103 | },
104 |
105 | /**
106 | * Set response status message
107 | *
108 | * @param {String} msg
109 | * @api public
110 | */
111 |
112 | set message(msg: string) {
113 | this.res.statusMessage = msg;
114 | },
115 |
116 | /**
117 | * Get response body.
118 | *
119 | * @return {Mixed}
120 | * @api public
121 | */
122 |
123 | get body(): any {
124 | return this._body;
125 | },
126 |
127 | /**
128 | * Set response body.
129 | *
130 | * @param {String|Buffer|Object|Stream} val
131 | * @api public
132 | */
133 |
134 | set body(val: any) {
135 | const original = this._body;
136 | this._body = val;
137 |
138 | // no content
139 | if (null == val) {
140 | if (!statuses.empty[this.status]) this.status = 204;
141 | this.remove('Content-Type');
142 | this.remove('Content-Length');
143 | this.remove('Transfer-Encoding');
144 | return;
145 | }
146 |
147 | // set the status
148 | if (!this._explicitStatus) this.status = 200;
149 |
150 | // set the content-type only if not yet set
151 | const setType = !this.header['content-type'];
152 |
153 | // string
154 | if ('string' == typeof val) {
155 | if (setType) this.type = /^\s* this.ctx.onerror(err));
171 |
172 | // overwriting
173 | if (null != original && original != val) this.remove('Content-Length');
174 |
175 | if (setType) this.type = 'bin';
176 | return;
177 | }
178 |
179 | // json
180 | this.remove('Content-Length');
181 | this.type = 'json';
182 | },
183 |
184 | /**
185 | * Set Content-Length field to `n`.
186 | *
187 | * @param {Number} n
188 | * @api public
189 | */
190 |
191 | set length(n: number) {
192 | this.set('Content-Length', n);
193 | },
194 |
195 | /**
196 | * Return parsed response Content-Length when present.
197 | *
198 | * @return {Number}
199 | * @api public
200 | */
201 |
202 | get length(): number {
203 | const len = this.header['content-length'];
204 | const body = this.body;
205 |
206 | if (null == len) {
207 | if (!body) return;
208 | if ('string' == typeof body) return Buffer.byteLength(body);
209 | if (Buffer.isBuffer(body)) return body.length;
210 | if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
211 | return;
212 | }
213 |
214 | return Math.trunc(len) || 0;
215 | },
216 |
217 | /**
218 | * Check if a header has been written to the socket.
219 | *
220 | * @return {Boolean}
221 | * @api public
222 | */
223 |
224 | get headerSent(): boolean {
225 | return this.res.headersSent;
226 | },
227 |
228 | /**
229 | * Vary on `field`.
230 | *
231 | * @param {String} field
232 | * @api public
233 | */
234 |
235 | vary(field: string): void {
236 | if (this.headerSent) return;
237 |
238 | vary(this.res, field);
239 | },
240 |
241 | /**
242 | * Perform a 302 redirect to `url`.
243 | *
244 | * The string "back" is special-cased
245 | * to provide Referrer support, when Referrer
246 | * is not present `alt` or "/" is used.
247 | *
248 | * Examples:
249 | *
250 | * this.redirect('back');
251 | * this.redirect('back', '/index.html');
252 | * this.redirect('/login');
253 | * this.redirect('http://google.com');
254 | *
255 | * @param {String} url
256 | * @param {String} [alt]
257 | * @api public
258 | */
259 |
260 | redirect(url: string, alt: string): void {
261 | // location
262 | if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
263 | this.set('Location', url);
264 |
265 | // status
266 | if (!statuses.redirect[this.status]) this.status = 302;
267 |
268 | // html
269 | if (this.ctx.accepts('html')) {
270 | url = escape(url);
271 | this.type = 'text/html; charset=utf-8';
272 | this.body = `Redirecting to ${url}.`;
273 | return;
274 | }
275 |
276 | // text
277 | this.type = 'text/plain; charset=utf-8';
278 | this.body = `Redirecting to ${url}.`;
279 | },
280 |
281 | /**
282 | * Set Content-Disposition header to "attachment" with optional `filename`.
283 | *
284 | * @param {String} filename
285 | * @api public
286 | */
287 |
288 | attachment(filename: string, options: contentDisposition.Options): void {
289 | if (filename) this.type = extname(filename);
290 | this.set('Content-Disposition', contentDisposition(filename, options));
291 | },
292 |
293 | /**
294 | * Set Content-Type response header with `type` through `mime.lookup()`
295 | * when it does not contain a charset.
296 | *
297 | * Examples:
298 | *
299 | * this.type = '.html';
300 | * this.type = 'html';
301 | * this.type = 'json';
302 | * this.type = 'application/json';
303 | * this.type = 'png';
304 | *
305 | * @param {String} type
306 | * @api public
307 | */
308 |
309 | set type(type: string) {
310 | type = getType(type);
311 | if (type) {
312 | this.set('Content-Type', type);
313 | } else {
314 | this.remove('Content-Type');
315 | }
316 | },
317 |
318 | /**
319 | * Set the Last-Modified date using a string or a Date.
320 | *
321 | * this.response.lastModified = new Date();
322 | * this.response.lastModified = '2013-09-13';
323 | *
324 | * @param {String|Date} type
325 | * @api public
326 | */
327 |
328 | set lastModified(val: string | Date) {
329 | if ('string' == typeof val) val = new Date(val);
330 | this.set('Last-Modified', val.toUTCString());
331 | },
332 |
333 | /**
334 | * Get the Last-Modified date in Date form, if it exists.
335 | *
336 | * @return {Date}
337 | * @api public
338 | */
339 |
340 | get lastModified(): string | Date {
341 | const date = this.get('last-modified');
342 | if (date) return new Date(date);
343 | },
344 |
345 | /**
346 | * Set the ETag of a response.
347 | * This will normalize the quotes if necessary.
348 | *
349 | * this.response.etag = 'md5hashsum';
350 | * this.response.etag = '"md5hashsum"';
351 | * this.response.etag = 'W/"123456789"';
352 | *
353 | * @param {String} etag
354 | * @api public
355 | */
356 |
357 | set etag(val: string) {
358 | if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
359 | this.set('ETag', val);
360 | },
361 |
362 | /**
363 | * Get the ETag of a response.
364 | *
365 | * @return {String}
366 | * @api public
367 | */
368 |
369 | get etag(): string {
370 | return this.get('ETag');
371 | },
372 |
373 | /**
374 | * Return the response mime type void of
375 | * parameters such as "charset".
376 | *
377 | * @return {String}
378 | * @api public
379 | */
380 |
381 | get type(): string {
382 | const type = this.get('Content-Type');
383 | if (!type) return '';
384 | return type.split(';', 1)[0];
385 | },
386 |
387 | /**
388 | * Check whether the response is one of the listed types.
389 | * Pretty much the same as `this.request.is()`.
390 | *
391 | * @param {String|Array} types...
392 | * @return {String|false}
393 | * @api public
394 | */
395 |
396 | is(types: any): string | false {
397 | const type = this.type;
398 | if (!types) return type || false;
399 | if (!Array.isArray(types)) types = [].slice.call(arguments);
400 | return typeis(type, types);
401 | },
402 |
403 | /**
404 | * Return response header.
405 | *
406 | * Examples:
407 | *
408 | * this.get('Content-Type');
409 | * // => "text/plain"
410 | *
411 | * this.get('content-type');
412 | * // => "text/plain"
413 | *
414 | * @param {String} field
415 | * @return {String}
416 | * @api public
417 | */
418 |
419 | get(field: { toLowerCase: () => string | number; }): string {
420 | return this.header[field.toLowerCase()] || '';
421 | },
422 |
423 | /**
424 | * Set header `field` to `val`, or pass
425 | * an object of header fields.
426 | *
427 | * Examples:
428 | *
429 | * this.set('Foo', ['bar', 'baz']);
430 | * this.set('Accept', 'application/json');
431 | * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
432 | *
433 | * @param {String|Object|Array} field
434 | * @param {String} val
435 | * @api public
436 | */
437 |
438 | set(field: any, val: string | Array): void {
439 | if (this.headerSent) return;
440 |
441 | if (2 == arguments.length) {
442 | if (Array.isArray(val)) val = val.map(v => typeof v === 'string' ? v : String(v));
443 | else if (typeof val !== 'string') val = String(val);
444 | this.res.setHeader(field, val);
445 | } else {
446 | for (const key in field) {
447 | this.set(key, field[key]);
448 | }
449 | }
450 | },
451 |
452 | /**
453 | * Append additional header `field` with value `val`.
454 | *
455 | * Examples:
456 | *
457 | * ```
458 | * this.append('Link', ['', '']);
459 | * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
460 | * this.append('Warning', '199 Miscellaneous warning');
461 | * ```
462 | *
463 | * @param {String} field
464 | * @param {String|Array} val
465 | * @api public
466 | */
467 |
468 | append(field: string, val: string | Array) {
469 | const prev = this.get(field);
470 |
471 | if (prev) {
472 | val = Array.isArray(prev)
473 | ? prev.concat(val)
474 | : [prev].concat(val);
475 | }
476 |
477 | return this.set(field, val);
478 | },
479 |
480 | /**
481 | * Remove header `field`.
482 | *
483 | * @param {String} name
484 | * @api public
485 | */
486 |
487 | remove(field: any) {
488 | if (this.headerSent) return;
489 |
490 | this.res.removeHeader(field);
491 | },
492 |
493 | /**
494 | * Checks if the request is writable.
495 | * Tests for the existence of the socket
496 | * as node sometimes does not set it.
497 | *
498 | * @return {Boolean}
499 | * @api private
500 | */
501 |
502 | get writable(): boolean {
503 | // can't write any more after response finished
504 | if (this.res.finished) return false;
505 |
506 | const socket = this.res.socket;
507 | // There are already pending outgoing res, but still writable
508 | // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
509 | if (!socket) return true;
510 | return socket.writable;
511 | },
512 |
513 | /**
514 | * Inspect implementation.
515 | *
516 | * @return {Object}
517 | * @api public
518 | */
519 |
520 | inspect(): object {
521 | if (!this.res) return;
522 | const o = this.toJSON();
523 | o.body = this.body;
524 | return o;
525 | },
526 |
527 | /**
528 | * Return JSON representation.
529 | *
530 | * @return {Object}
531 | * @api public
532 | */
533 |
534 | toJSON(): object {
535 | return only(this, [
536 | 'status',
537 | 'message',
538 | 'header'
539 | ]);
540 | },
541 |
542 | /**
543 | * Flush any set headers, and begin the body
544 | */
545 | flushHeaders() {
546 | this.res.flushHeaders();
547 | }
548 | };
549 |
550 | /**
551 | * Custom inspection implementation for newer Node.js versions.
552 | *
553 | * @return {Object}
554 | * @api public
555 | */
556 | if (util.inspect.custom) {
557 | exports[util.inspect.custom] = exports.inspect;
558 | }
559 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tkoa",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/accepts": {
8 | "version": "1.3.5",
9 | "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
10 | "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
11 | "requires": {
12 | "@types/node": "*"
13 | }
14 | },
15 | "@types/body-parser": {
16 | "version": "1.17.0",
17 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
18 | "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
19 | "requires": {
20 | "@types/connect": "*",
21 | "@types/node": "*"
22 | }
23 | },
24 | "@types/connect": {
25 | "version": "3.4.32",
26 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
27 | "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
28 | "requires": {
29 | "@types/node": "*"
30 | }
31 | },
32 | "@types/content-disposition": {
33 | "version": "0.5.2",
34 | "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.2.tgz",
35 | "integrity": "sha1-GMyw/RJoCOSYqCDLgUnAByorkGY="
36 | },
37 | "@types/content-type": {
38 | "version": "1.1.3",
39 | "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.3.tgz",
40 | "integrity": "sha512-pv8VcFrZ3fN93L4rTNIbbUzdkzjEyVMp5mPVjsFfOYTDOZMZiZ8P1dhu+kEv3faYyKzZgLlSvnyQNFg+p/v5ug=="
41 | },
42 | "@types/cookies": {
43 | "version": "0.7.1",
44 | "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.1.tgz",
45 | "integrity": "sha512-ku6IvbucEyuC6i4zAVK/KnuzWNXdbFd1HkXlNLg/zhWDGTtQT5VhumiPruB/BHW34PWVFwyfwGftDQHfWNxu3Q==",
46 | "requires": {
47 | "@types/connect": "*",
48 | "@types/express": "*",
49 | "@types/keygrip": "*",
50 | "@types/node": "*"
51 | }
52 | },
53 | "@types/destroy": {
54 | "version": "1.0.0",
55 | "resolved": "https://registry.npmjs.org/@types/destroy/-/destroy-1.0.0.tgz",
56 | "integrity": "sha512-nE3ePJLWPRu/qFHN8mj3fWnkr9K9ezwoiG4yOis2DuLeAawlnOOT/pM29JQkityrwfEvkblU5O9iS1bsiMqtDw==",
57 | "requires": {
58 | "@types/node": "*"
59 | }
60 | },
61 | "@types/escape-html": {
62 | "version": "0.0.20",
63 | "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-0.0.20.tgz",
64 | "integrity": "sha512-6dhZJLbA7aOwkYB2GDGdIqJ20wmHnkDzaxV9PJXe7O02I2dSFTERzRB6JrX6cWKaS+VqhhY7cQUMCbO5kloFUw=="
65 | },
66 | "@types/express": {
67 | "version": "4.16.1",
68 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz",
69 | "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==",
70 | "requires": {
71 | "@types/body-parser": "*",
72 | "@types/express-serve-static-core": "*",
73 | "@types/serve-static": "*"
74 | }
75 | },
76 | "@types/express-serve-static-core": {
77 | "version": "4.16.2",
78 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz",
79 | "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==",
80 | "requires": {
81 | "@types/node": "*",
82 | "@types/range-parser": "*"
83 | }
84 | },
85 | "@types/fresh": {
86 | "version": "0.5.0",
87 | "resolved": "https://registry.npmjs.org/@types/fresh/-/fresh-0.5.0.tgz",
88 | "integrity": "sha512-eGPzuyc6wZM3sSHJdF7NM2jW6B/xsB014Rqg/iDa6xY02mlfy1w/TE2sYhR8vbHxkzJOXiGo6NuIk3xk35vsgQ=="
89 | },
90 | "@types/http-assert": {
91 | "version": "1.4.0",
92 | "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.4.0.tgz",
93 | "integrity": "sha512-TZDqvFW4nQwL9DVSNJIJu4lPLttKgzRF58COa7Vs42Ki/MrhIqUbeIw0MWn4kGLiZLXB7oCBibm7nkSjPkzfKQ=="
94 | },
95 | "@types/http-errors": {
96 | "version": "1.6.1",
97 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.6.1.tgz",
98 | "integrity": "sha512-s+RHKSGc3r0m3YEE2UXomJYrpQaY9cDmNDLU2XvG1/LAZsQ7y8emYkTLfcw/ByDtcsTyRQKwr76Bj4PkN2hfWg=="
99 | },
100 | "@types/keygrip": {
101 | "version": "1.0.1",
102 | "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz",
103 | "integrity": "sha1-/1QEYtL7TQqIRBzq8n0oewHD2Hg="
104 | },
105 | "@types/koa": {
106 | "version": "2.0.48",
107 | "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.0.48.tgz",
108 | "integrity": "sha512-CiIUYhHlOFJhSCTmsFoFkV2t9ij1JwW26nt0W9XZoWTvmAw6zTE0+k3IAoGICtjzIfhZpZcO323NHmI1LGmdDw==",
109 | "requires": {
110 | "@types/accepts": "*",
111 | "@types/cookies": "*",
112 | "@types/http-assert": "*",
113 | "@types/keygrip": "*",
114 | "@types/koa-compose": "*",
115 | "@types/node": "*"
116 | }
117 | },
118 | "@types/koa-compose": {
119 | "version": "3.2.3",
120 | "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.3.tgz",
121 | "integrity": "sha512-kXvR0DPyZ3gaFxZs4WycA8lpzlPGtFmwdbgce+NWd+TG3PycPO3o5FkePH60HoBPd8BBaSiw3vhtgM42O2kQcg==",
122 | "requires": {
123 | "@types/koa": "*"
124 | }
125 | },
126 | "@types/koa-convert": {
127 | "version": "1.2.2",
128 | "resolved": "https://registry.npmjs.org/@types/koa-convert/-/koa-convert-1.2.2.tgz",
129 | "integrity": "sha512-RhoEpAMFfj6CanUHwJBbMGaZ2y1WtD08n2NhLscYa/u8XSz0Mfuhg6M0Xm2HRPohAswEdgPngAxVe8b+04c7cQ==",
130 | "requires": {
131 | "@types/koa": "*"
132 | }
133 | },
134 | "@types/mime": {
135 | "version": "2.0.1",
136 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
137 | "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
138 | },
139 | "@types/node": {
140 | "version": "11.13.4",
141 | "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz",
142 | "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ=="
143 | },
144 | "@types/on-finished": {
145 | "version": "2.3.1",
146 | "resolved": "https://registry.npmjs.org/@types/on-finished/-/on-finished-2.3.1.tgz",
147 | "integrity": "sha512-mzVYaYcFs5Jd2n/O6uYIRUsFRR1cHyZLRvkLCU0E7+G5WhY0qBDAR5fUCeZbvecYOSh9ikhlesyi2UfI8B9ckQ==",
148 | "requires": {
149 | "@types/node": "*"
150 | }
151 | },
152 | "@types/parseurl": {
153 | "version": "1.3.1",
154 | "resolved": "https://registry.npmjs.org/@types/parseurl/-/parseurl-1.3.1.tgz",
155 | "integrity": "sha1-48sRAhYOSO+ln0l8TsIt7k87Wyc=",
156 | "requires": {
157 | "@types/node": "*"
158 | }
159 | },
160 | "@types/range-parser": {
161 | "version": "1.2.3",
162 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
163 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
164 | },
165 | "@types/serve-static": {
166 | "version": "1.13.2",
167 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
168 | "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
169 | "requires": {
170 | "@types/express-serve-static-core": "*",
171 | "@types/mime": "*"
172 | }
173 | },
174 | "@types/statuses": {
175 | "version": "1.5.0",
176 | "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-1.5.0.tgz",
177 | "integrity": "sha512-4zJN5gJH+Km6hA36z8MnOKas6EU0qwxItTXNijYDPuZUsSk4EpIAB56fwnxZIhi3tHx42J7wqNdQTqt49Ar9FQ=="
178 | },
179 | "@types/type-is": {
180 | "version": "1.6.2",
181 | "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.2.tgz",
182 | "integrity": "sha512-q8d51ZdF/D8xebrtNDsZH+4XBUFdz8xEgWhE4U4F4WWmcBZ8+i/r/qs9DmjAprYh5qQTYlY4BxaVKDrWIwNQ9w==",
183 | "requires": {
184 | "@types/node": "*"
185 | }
186 | },
187 | "@types/vary": {
188 | "version": "1.1.0",
189 | "resolved": "https://registry.npmjs.org/@types/vary/-/vary-1.1.0.tgz",
190 | "integrity": "sha512-LQWqrIa0dvEOOH37lGksMEXbypRLUFqu6Gx0pmX7zIUisD2I/qaVgEX/vJ/PSVSW0Hk6yz1BNkFpqg6dZm3Wug==",
191 | "requires": {
192 | "@types/node": "*"
193 | }
194 | },
195 | "accepts": {
196 | "version": "1.3.5",
197 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
198 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
199 | "requires": {
200 | "mime-types": "~2.1.18",
201 | "negotiator": "0.6.1"
202 | }
203 | },
204 | "any-promise": {
205 | "version": "1.3.0",
206 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
207 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
208 | },
209 | "cache-content-type": {
210 | "version": "1.0.1",
211 | "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
212 | "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
213 | "requires": {
214 | "mime-types": "^2.1.18",
215 | "ylru": "^1.2.0"
216 | }
217 | },
218 | "co": {
219 | "version": "4.6.0",
220 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
221 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
222 | },
223 | "content-disposition": {
224 | "version": "0.5.3",
225 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
226 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
227 | "requires": {
228 | "safe-buffer": "5.1.2"
229 | }
230 | },
231 | "content-type": {
232 | "version": "1.0.4",
233 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
234 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
235 | },
236 | "cookies": {
237 | "version": "0.7.3",
238 | "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz",
239 | "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==",
240 | "requires": {
241 | "depd": "~1.1.2",
242 | "keygrip": "~1.0.3"
243 | },
244 | "dependencies": {
245 | "depd": {
246 | "version": "1.1.2",
247 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
248 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
249 | }
250 | }
251 | },
252 | "debug": {
253 | "version": "4.1.1",
254 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
255 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
256 | "requires": {
257 | "ms": "^2.1.1"
258 | }
259 | },
260 | "deep-equal": {
261 | "version": "1.0.1",
262 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
263 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
264 | },
265 | "delegates": {
266 | "version": "1.0.0",
267 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
268 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
269 | },
270 | "destroy": {
271 | "version": "1.0.4",
272 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
273 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
274 | },
275 | "ee-first": {
276 | "version": "1.1.1",
277 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
278 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
279 | },
280 | "error-inject": {
281 | "version": "1.0.0",
282 | "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz",
283 | "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc="
284 | },
285 | "escape-html": {
286 | "version": "1.0.3",
287 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
288 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
289 | },
290 | "fresh": {
291 | "version": "0.5.2",
292 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
293 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
294 | },
295 | "http-assert": {
296 | "version": "1.4.0",
297 | "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz",
298 | "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==",
299 | "requires": {
300 | "deep-equal": "~1.0.1",
301 | "http-errors": "~1.7.1"
302 | }
303 | },
304 | "http-errors": {
305 | "version": "1.7.2",
306 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
307 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
308 | "requires": {
309 | "depd": "~1.1.2",
310 | "inherits": "2.0.3",
311 | "setprototypeof": "1.1.1",
312 | "statuses": ">= 1.5.0 < 2",
313 | "toidentifier": "1.0.0"
314 | },
315 | "dependencies": {
316 | "depd": {
317 | "version": "1.1.2",
318 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
319 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
320 | }
321 | }
322 | },
323 | "inherits": {
324 | "version": "2.0.3",
325 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
326 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
327 | },
328 | "keygrip": {
329 | "version": "1.0.3",
330 | "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz",
331 | "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g=="
332 | },
333 | "koa-compose": {
334 | "version": "4.1.0",
335 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
336 | "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
337 | },
338 | "koa-convert": {
339 | "version": "1.2.0",
340 | "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz",
341 | "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=",
342 | "requires": {
343 | "co": "^4.6.0",
344 | "koa-compose": "^3.0.0"
345 | },
346 | "dependencies": {
347 | "koa-compose": {
348 | "version": "3.2.1",
349 | "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz",
350 | "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=",
351 | "requires": {
352 | "any-promise": "^1.1.0"
353 | }
354 | }
355 | }
356 | },
357 | "koa-is-json": {
358 | "version": "1.0.0",
359 | "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz",
360 | "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ="
361 | },
362 | "media-typer": {
363 | "version": "0.3.0",
364 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
365 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
366 | },
367 | "mime-db": {
368 | "version": "1.38.0",
369 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
370 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
371 | },
372 | "mime-types": {
373 | "version": "2.1.22",
374 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
375 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
376 | "requires": {
377 | "mime-db": "~1.38.0"
378 | }
379 | },
380 | "ms": {
381 | "version": "2.1.1",
382 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
383 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
384 | },
385 | "negotiator": {
386 | "version": "0.6.1",
387 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
388 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
389 | },
390 | "on-finished": {
391 | "version": "2.3.0",
392 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
393 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
394 | "requires": {
395 | "ee-first": "1.1.1"
396 | }
397 | },
398 | "only": {
399 | "version": "0.0.2",
400 | "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
401 | "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q="
402 | },
403 | "parseurl": {
404 | "version": "1.3.2",
405 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
406 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
407 | },
408 | "safe-buffer": {
409 | "version": "5.1.2",
410 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
411 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
412 | },
413 | "setprototypeof": {
414 | "version": "1.1.1",
415 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
416 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
417 | },
418 | "statuses": {
419 | "version": "1.5.0",
420 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
421 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
422 | },
423 | "toidentifier": {
424 | "version": "1.0.0",
425 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
426 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
427 | },
428 | "type-is": {
429 | "version": "1.6.16",
430 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
431 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
432 | "requires": {
433 | "media-typer": "0.3.0",
434 | "mime-types": "~2.1.18"
435 | }
436 | },
437 | "vary": {
438 | "version": "1.1.2",
439 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
440 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
441 | },
442 | "ylru": {
443 | "version": "1.2.1",
444 | "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz",
445 | "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ=="
446 | }
447 | }
448 | }
449 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tkoa",
3 | "version": "1.0.1",
4 | "description": "Koa web app framework written in typescript.",
5 | "main": "./lib/application.js",
6 | "types": "./lib/application.ts",
7 | "scripts": {
8 | "test": "ava test.js"
9 | },
10 | "keywords": [
11 | "koa",
12 | "web",
13 | "app",
14 | "http",
15 | "application",
16 | "framework",
17 | "middleware",
18 | "typescript",
19 | "rack"
20 | ],
21 | "author": "All Contributor",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@types/content-disposition": "^0.5.2",
25 | "@types/content-type": "^1.1.3",
26 | "@types/destroy": "^1.0.0",
27 | "@types/escape-html": "0.0.20",
28 | "@types/fresh": "^0.5.0",
29 | "@types/http-errors": "^1.6.1",
30 | "@types/koa-convert": "^1.2.2",
31 | "@types/node": "^11.13.4",
32 | "@types/on-finished": "^2.3.1",
33 | "@types/parseurl": "^1.3.1",
34 | "@types/statuses": "^1.5.0",
35 | "@types/type-is": "^1.6.2",
36 | "@types/vary": "^1.1.0",
37 | "accepts": "^1.3.5",
38 | "cache-content-type": "^1.0.1",
39 | "content-disposition": "^0.5.3",
40 | "content-type": "^1.0.4",
41 | "cookies": "^0.7.3",
42 | "debug": "^4.1.1",
43 | "delegates": "^1.0.0",
44 | "destroy": "^1.0.4",
45 | "error-inject": "^1.0.0",
46 | "escape-html": "^1.0.3",
47 | "fresh": "^0.5.2",
48 | "http-assert": "^1.4.0",
49 | "http-errors": "^1.7.2",
50 | "koa-compose": "^4.1.0",
51 | "koa-convert": "^1.2.0",
52 | "koa-is-json": "^1.0.0",
53 | "on-finished": "^2.3.0",
54 | "only": "0.0.2",
55 | "parseurl": "^1.3.2",
56 | "statuses": "^1.5.0",
57 | "type-is": "^1.6.16",
58 | "typescript": "^3.4.3",
59 | "vary": "^1.1.2"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/source/logo small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkoajs/tkoa/09472fc2c8a414cae6a8195cfb25e433dd5eba3e/source/logo small.png
--------------------------------------------------------------------------------
/source/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkoajs/tkoa/09472fc2c8a414cae6a8195cfb25e433dd5eba3e/source/logo.png
--------------------------------------------------------------------------------
/source/tkoa logo square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkoajs/tkoa/09472fc2c8a414cae6a8195cfb25e433dd5eba3e/source/tkoa logo square.png
--------------------------------------------------------------------------------
/source/ts logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkoajs/tkoa/09472fc2c8a414cae6a8195cfb25e433dd5eba3e/source/ts logo.png
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | let tKoa = require('./lib/application.js');
4 |
5 | function newServer() {
6 | try {
7 | let app = new tKoa();
8 | } catch (e) {
9 | return 'fail';
10 | }
11 | return 'pass';
12 | }
13 |
14 | test('newServer testing', t => {
15 | t.is(newServer(), 'pass');
16 | });
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./lib",
4 | "allowJs": true,
5 | "target": "es6",
6 | "noEmitOnError": true,
7 | "module": "commonjs"
8 | },
9 | "include": [
10 | "./lib/**/*"
11 | ],
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------