UNPKG

21.1 kBTypeScriptView Raw
1/* =================== USAGE ===================
2
3 import * as Koa from "koa"
4 const app = new Koa()
5
6 async function (ctx: Koa.Context, next: Koa.Next) {
7 // ...
8 }
9
10 =============================================== */
11/// <reference types="node" />
12import * as accepts from "accepts";
13import { AsyncLocalStorage } from "async_hooks";
14import * as Cookies from "cookies";
15import { EventEmitter } from "events";
16import { IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders, Server, ServerResponse } from "http";
17import { Http2ServerRequest, Http2ServerResponse } from "http2";
18import httpAssert = require("http-assert");
19import * as contentDisposition from "content-disposition";
20import * as HttpErrors from "http-errors";
21import * as Keygrip from "keygrip";
22import * as compose from "koa-compose";
23import { ListenOptions, Socket } from "net";
24import { ParsedUrlQuery } from "querystring";
25import * as url from "url";
26
27declare interface ContextDelegatedRequest {
28 /**
29 * Return request header.
30 */
31 header: IncomingHttpHeaders;
32
33 /**
34 * Return request header, alias as request.header
35 */
36 headers: IncomingHttpHeaders;
37
38 /**
39 * Get/Set request URL.
40 */
41 url: string;
42
43 /**
44 * Get origin of URL.
45 */
46 origin: string;
47
48 /**
49 * Get full request URL.
50 */
51 href: string;
52
53 /**
54 * Get/Set request method.
55 */
56 method: string;
57
58 /**
59 * Get request pathname.
60 * Set pathname, retaining the query-string when present.
61 */
62 path: string;
63
64 /**
65 * Get parsed query-string.
66 * Set query-string as an object.
67 */
68 query: ParsedUrlQuery;
69
70 /**
71 * Get/Set query string.
72 */
73 querystring: string;
74
75 /**
76 * Get the search string. Same as the querystring
77 * except it includes the leading ?.
78 *
79 * Set the search string. Same as
80 * response.querystring= but included for ubiquity.
81 */
82 search: string;
83
84 /**
85 * Parse the "Host" header field host
86 * and support X-Forwarded-Host when a
87 * proxy is enabled.
88 */
89 host: string;
90
91 /**
92 * Parse the "Host" header field hostname
93 * and support X-Forwarded-Host when a
94 * proxy is enabled.
95 */
96 hostname: string;
97
98 /**
99 * Get WHATWG parsed URL object.
100 */
101 URL: url.URL;
102
103 /**
104 * Check if the request is fresh, aka
105 * Last-Modified and/or the ETag
106 * still match.
107 */
108 fresh: boolean;
109
110 /**
111 * Check if the request is stale, aka
112 * "Last-Modified" and / or the "ETag" for the
113 * resource has changed.
114 */
115 stale: boolean;
116
117 /**
118 * Check if the request is idempotent.
119 */
120 idempotent: boolean;
121
122 /**
123 * Return the request socket.
124 */
125 socket: Socket;
126
127 /**
128 * Return the protocol string "http" or "https"
129 * when requested with TLS. When the proxy setting
130 * is enabled the "X-Forwarded-Proto" header
131 * field will be trusted. If you're running behind
132 * a reverse proxy that supplies https for you this
133 * may be enabled.
134 */
135 protocol: string;
136
137 /**
138 * Short-hand for:
139 *
140 * this.protocol == 'https'
141 */
142 secure: boolean;
143
144 /**
145 * Request remote address. Supports X-Forwarded-For when app.proxy is true.
146 */
147 ip: string;
148
149 /**
150 * When `app.proxy` is `true`, parse
151 * the "X-Forwarded-For" ip address list.
152 *
153 * For example if the value were "client, proxy1, proxy2"
154 * you would receive the array `["client", "proxy1", "proxy2"]`
155 * where "proxy2" is the furthest down-stream.
156 */
157 ips: string[];
158
159 /**
160 * Return subdomains as an array.
161 *
162 * Subdomains are the dot-separated parts of the host before the main domain
163 * of the app. By default, the domain of the app is assumed to be the last two
164 * parts of the host. This can be changed by setting `app.subdomainOffset`.
165 *
166 * For example, if the domain is "tobi.ferrets.example.com":
167 * If `app.subdomainOffset` is not set, this.subdomains is
168 * `["ferrets", "tobi"]`.
169 * If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`.
170 */
171 subdomains: string[];
172
173 /**
174 * Check if the given `type(s)` is acceptable, returning
175 * the best match when true, otherwise `false`, in which
176 * case you should respond with 406 "Not Acceptable".
177 *
178 * The `type` value may be a single mime type string
179 * such as "application/json", the extension name
180 * such as "json" or an array `["json", "html", "text/plain"]`. When a list
181 * or array is given the _best_ match, if any is returned.
182 *
183 * Examples:
184 *
185 * // Accept: text/html
186 * this.accepts('html');
187 * // => "html"
188 *
189 * // Accept: text/*, application/json
190 * this.accepts('html');
191 * // => "html"
192 * this.accepts('text/html');
193 * // => "text/html"
194 * this.accepts('json', 'text');
195 * // => "json"
196 * this.accepts('application/json');
197 * // => "application/json"
198 *
199 * // Accept: text/*, application/json
200 * this.accepts('image/png');
201 * this.accepts('png');
202 * // => undefined
203 *
204 * // Accept: text/*;q=.5, application/json
205 * this.accepts(['html', 'json']);
206 * this.accepts('html', 'json');
207 * // => "json"
208 */
209 accepts(): string[];
210 accepts(...types: string[]): string | false;
211 accepts(types: string[]): string | false;
212
213 /**
214 * Return accepted encodings or best fit based on `encodings`.
215 *
216 * Given `Accept-Encoding: gzip, deflate`
217 * an array sorted by quality is returned:
218 *
219 * ['gzip', 'deflate']
220 */
221 acceptsEncodings(): string[];
222 acceptsEncodings(...encodings: string[]): string | false;
223 acceptsEncodings(encodings: string[]): string | false;
224
225 /**
226 * Return accepted charsets or best fit based on `charsets`.
227 *
228 * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
229 * an array sorted by quality is returned:
230 *
231 * ['utf-8', 'utf-7', 'iso-8859-1']
232 */
233 acceptsCharsets(): string[];
234 acceptsCharsets(...charsets: string[]): string | false;
235 acceptsCharsets(charsets: string[]): string | false;
236
237 /**
238 * Return accepted languages or best fit based on `langs`.
239 *
240 * Given `Accept-Language: en;q=0.8, es, pt`
241 * an array sorted by quality is returned:
242 *
243 * ['es', 'pt', 'en']
244 */
245 acceptsLanguages(): string[];
246 acceptsLanguages(...langs: string[]): string | false;
247 acceptsLanguages(langs: string[]): string | false;
248
249 /**
250 * Check if the incoming request contains the "Content-Type"
251 * header field, and it contains any of the give mime `type`s.
252 * If there is no request body, `null` is returned.
253 * If there is no content type, `false` is returned.
254 * Otherwise, it returns the first `type` that matches.
255 *
256 * Examples:
257 *
258 * // With Content-Type: text/html; charset=utf-8
259 * this.is('html'); // => 'html'
260 * this.is('text/html'); // => 'text/html'
261 * this.is('text/*', 'application/json'); // => 'text/html'
262 *
263 * // When Content-Type is application/json
264 * this.is('json', 'urlencoded'); // => 'json'
265 * this.is('application/json'); // => 'application/json'
266 * this.is('html', 'application/*'); // => 'application/json'
267 *
268 * this.is('html'); // => false
269 */
270 // is(): string | boolean;
271 is(...types: string[]): string | false | null;
272 is(types: string[]): string | false | null;
273
274 /**
275 * Return request header. If the header is not set, will return an empty
276 * string.
277 *
278 * The `Referrer` header field is special-cased, both `Referrer` and
279 * `Referer` are interchangeable.
280 *
281 * Examples:
282 *
283 * this.get('Content-Type');
284 * // => "text/plain"
285 *
286 * this.get('content-type');
287 * // => "text/plain"
288 *
289 * this.get('Something');
290 * // => ''
291 */
292 get(field: string): string;
293}
294
295declare interface ContextDelegatedResponse {
296 /**
297 * Get/Set response status code.
298 */
299 status: number;
300
301 /**
302 * Get response status message
303 */
304 message: string;
305
306 /**
307 * Get/Set response body.
308 */
309 body: unknown;
310
311 /**
312 * Return parsed response Content-Length when present.
313 * Set Content-Length field to `n`.
314 */
315 length: number;
316
317 /**
318 * Check if a header has been written to the socket.
319 */
320 headerSent: boolean;
321
322 /**
323 * Vary on `field`.
324 */
325 vary(field: string | string[]): void;
326
327 /**
328 * Perform a 302 redirect to `url`.
329 *
330 * The string "back" is special-cased
331 * to provide Referrer support, when Referrer
332 * is not present `alt` or "/" is used.
333 *
334 * Examples:
335 *
336 * this.redirect('back');
337 * this.redirect('back', '/index.html');
338 * this.redirect('/login');
339 * this.redirect('http://google.com');
340 */
341 redirect(url: string, alt?: string): void;
342
343 /**
344 * Set Content-Disposition to "attachment" to signal the client to prompt for download.
345 * Optionally specify the filename of the download and some options.
346 */
347 attachment(filename?: string, options?: contentDisposition.Options): void;
348
349 /**
350 * Return the response mime type void of
351 * parameters such as "charset".
352 *
353 * Set Content-Type response header with `type` through `mime.lookup()`
354 * when it does not contain a charset.
355 *
356 * Examples:
357 *
358 * this.type = '.html';
359 * this.type = 'html';
360 * this.type = 'json';
361 * this.type = 'application/json';
362 * this.type = 'png';
363 */
364 type: string;
365
366 /**
367 * Get the Last-Modified date in Date form, if it exists.
368 * Set the Last-Modified date using a string or a Date.
369 *
370 * this.response.lastModified = new Date();
371 * this.response.lastModified = '2013-09-13';
372 */
373 lastModified: Date;
374
375 /**
376 * Get/Set the ETag of a response.
377 * This will normalize the quotes if necessary.
378 *
379 * this.response.etag = 'md5hashsum';
380 * this.response.etag = '"md5hashsum"';
381 * this.response.etag = 'W/"123456789"';
382 *
383 * @param {String} etag
384 * @api public
385 */
386 etag: string;
387
388 /**
389 * Set header `field` to `val`, or pass
390 * an object of header fields.
391 *
392 * Examples:
393 *
394 * this.set('Foo', ['bar', 'baz']);
395 * this.set('Accept', 'application/json');
396 * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
397 */
398 set(field: { [key: string]: string | string[] }): void;
399 set(field: string, val: string | string[]): void;
400
401 /**
402 * Append additional header `field` with value `val`.
403 *
404 * Examples:
405 *
406 * ```
407 * this.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
408 * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
409 * this.append('Warning', '199 Miscellaneous warning');
410 * ```
411 */
412 append(field: string, val: string | string[]): void;
413
414 /**
415 * Remove header `field`.
416 */
417 remove(field: string): void;
418
419 /**
420 * Checks if the request is writable.
421 * Tests for the existence of the socket
422 * as node sometimes does not set it.
423 */
424 writable: boolean;
425
426 /**
427 * Flush any set headers, and begin the body
428 */
429 flushHeaders(): void;
430}
431
432declare class Application<
433 StateT = Application.DefaultState,
434 ContextT = Application.DefaultContext,
435> extends EventEmitter {
436 proxy: boolean;
437 proxyIpHeader: string;
438 maxIpsCount: number;
439 middleware: Array<Application.Middleware<StateT, ContextT>>;
440 subdomainOffset: number;
441 env: string;
442 context: Application.BaseContext & ContextT;
443 request: Application.BaseRequest;
444 response: Application.BaseResponse;
445 silent: boolean;
446 keys: Keygrip | string[];
447 ctxStorage: AsyncLocalStorage<ContextT> | undefined;
448
449 /**
450 * @param {object} [options] Application options
451 * @param {string} [options.env='development'] Environment
452 * @param {string[]} [options.keys] Signed cookie keys
453 * @param {boolean} [options.proxy] Trust proxy headers
454 * @param {number} [options.subdomainOffset] Subdomain offset
455 * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
456 * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
457 * @param {boolean} [options.asyncLocalStorage] Enable AsyncLocalStorage
458 */
459 constructor(options?: {
460 env?: string | undefined;
461 keys?: string[] | undefined;
462 proxy?: boolean | undefined;
463 subdomainOffset?: number | undefined;
464 proxyIpHeader?: string | undefined;
465 maxIpsCount?: number | undefined;
466 asyncLocalStorage?: boolean | undefined;
467 });
468
469 /**
470 * Shorthand for:
471 *
472 * http.createServer(app.callback()).listen(...)
473 */
474 listen(port?: number, hostname?: string, backlog?: number, listeningListener?: () => void): Server;
475 listen(port: number, hostname?: string, listeningListener?: () => void): Server;
476 listen(port: number, backlog?: number, listeningListener?: () => void): Server;
477 listen(port: number, listeningListener?: () => void): Server;
478 listen(path: string, backlog?: number, listeningListener?: () => void): Server;
479 listen(path: string, listeningListener?: () => void): Server;
480 listen(options: ListenOptions, listeningListener?: () => void): Server;
481 listen(handle: any, backlog?: number, listeningListener?: () => void): Server;
482 listen(handle: any, listeningListener?: () => void): Server;
483
484 /**
485 * Return JSON representation.
486 * We only bother showing settings.
487 */
488 inspect(): any;
489
490 /**
491 * Return JSON representation.
492 * We only bother showing settings.
493 */
494 toJSON(): any;
495
496 /**
497 * Use the given middleware `fn`.
498 *
499 * Old-style middleware will be converted.
500 */
501 use<NewStateT = {}, NewContextT = {}>(
502 middleware: Application.Middleware<StateT & NewStateT, ContextT & NewContextT>,
503 ): Application<StateT & NewStateT, ContextT & NewContextT>;
504
505 /**
506 * Return a request handler callback
507 * for node's native http/http2 server.
508 */
509 callback(): (req: IncomingMessage | Http2ServerRequest, res: ServerResponse | Http2ServerResponse) => Promise<void>;
510
511 /**
512 * Initialize a new context.
513 *
514 * @api private
515 */
516 createContext<StateT = Application.DefaultState>(
517 req: IncomingMessage,
518 res: ServerResponse,
519 ): Application.ParameterizedContext<StateT>;
520
521 /**
522 * Default error handler.
523 *
524 * @api private
525 */
526 onerror(err: Error): void;
527
528 /**
529 * return current context from async local storage
530 */
531 readonly currentContext: ContextT | undefined;
532}
533
534declare namespace Application {
535 type DefaultStateExtends = any;
536 /**
537 * This interface can be augmented by users to add types to Koa's default state
538 */
539 interface DefaultState extends DefaultStateExtends {}
540
541 type DefaultContextExtends = {};
542 /**
543 * This interface can be augmented by users to add types to Koa's default context
544 */
545 interface DefaultContext extends DefaultContextExtends {
546 /**
547 * Custom properties.
548 */
549 [key: PropertyKey]: any;
550 }
551
552 type Middleware<StateT = DefaultState, ContextT = DefaultContext, ResponseBodyT = any> = compose.Middleware<
553 ParameterizedContext<StateT, ContextT, ResponseBodyT>
554 >;
555
556 interface BaseRequest extends ContextDelegatedRequest {
557 /**
558 * Get the charset when present or undefined.
559 */
560 charset: string;
561
562 /**
563 * Return parsed Content-Length when present.
564 */
565 length: number;
566
567 /**
568 * Return the request mime type void of
569 * parameters such as "charset".
570 */
571 type: string;
572
573 /**
574 * Inspect implementation.
575 */
576 inspect(): any;
577
578 /**
579 * Return JSON representation.
580 */
581 toJSON(): any;
582 }
583
584 interface BaseResponse extends ContextDelegatedResponse {
585 /**
586 * Return the request socket.
587 *
588 * @return {Connection}
589 * @api public
590 */
591 socket: Socket;
592
593 /**
594 * Return response header.
595 */
596 header: OutgoingHttpHeaders;
597
598 /**
599 * Return response header, alias as response.header
600 */
601 headers: OutgoingHttpHeaders;
602
603 /**
604 * Check whether the response is one of the listed types.
605 * Pretty much the same as `this.request.is()`.
606 *
607 * @param {String|Array} types...
608 * @return {String|false}
609 * @api public
610 */
611 // is(): string;
612 is(...types: string[]): string | false | null;
613 is(types: string[]): string | false | null;
614
615 /**
616 * Return response header. If the header is not set, will return an empty
617 * string.
618 *
619 * The `Referrer` header field is special-cased, both `Referrer` and
620 * `Referer` are interchangeable.
621 *
622 * Examples:
623 *
624 * this.get('Content-Type');
625 * // => "text/plain"
626 *
627 * this.get('content-type');
628 * // => "text/plain"
629 *
630 * this.get('Something');
631 * // => ''
632 */
633 get(field: string): string;
634
635 /**
636 * Inspect implementation.
637 */
638 inspect(): any;
639
640 /**
641 * Return JSON representation.
642 */
643 toJSON(): any;
644 }
645
646 interface BaseContext extends ContextDelegatedRequest, ContextDelegatedResponse {
647 /**
648 * util.inspect() implementation, which
649 * just returns the JSON output.
650 */
651 inspect(): any;
652
653 /**
654 * Return JSON representation.
655 *
656 * Here we explicitly invoke .toJSON() on each
657 * object, as iteration will otherwise fail due
658 * to the getters and cause utilities such as
659 * clone() to fail.
660 */
661 toJSON(): any;
662
663 /**
664 * Similar to .throw(), adds assertion.
665 *
666 * this.assert(this.user, 401, 'Please login!');
667 *
668 * See: https://github.com/jshttp/http-assert
669 */
670 assert: typeof httpAssert;
671
672 /**
673 * Throw an error with `msg` and optional `status`
674 * defaulting to 500. Note that these are user-level
675 * errors, and the message may be exposed to the client.
676 *
677 * this.throw(403)
678 * this.throw('name required', 400)
679 * this.throw(400, 'name required')
680 * this.throw('something exploded')
681 * this.throw(new Error('invalid'), 400);
682 * this.throw(400, new Error('invalid'));
683 *
684 * See: https://github.com/jshttp/http-errors
685 */
686 throw(message: string, code?: number, properties?: {}): never;
687 throw(status: number): never;
688 throw(...properties: Array<number | string | {}>): never;
689
690 /**
691 * Default error handling.
692 */
693 onerror(err: Error): void;
694 }
695
696 interface Request extends BaseRequest {
697 app: Application;
698 req: IncomingMessage;
699 res: ServerResponse;
700 ctx: Context;
701 response: Response;
702 originalUrl: string;
703 ip: string;
704 accept: accepts.Accepts;
705 }
706
707 interface Response extends BaseResponse {
708 app: Application;
709 req: IncomingMessage;
710 res: ServerResponse;
711 ctx: Context;
712 request: Request;
713 }
714
715 interface ExtendableContext extends BaseContext {
716 app: Application;
717 request: Request;
718 response: Response;
719 req: IncomingMessage;
720 res: ServerResponse;
721 originalUrl: string;
722 cookies: Cookies;
723 accept: accepts.Accepts;
724 /**
725 * To bypass Koa's built-in response handling, you may explicitly set `ctx.respond = false;`
726 */
727 respond?: boolean | undefined;
728 }
729
730 type ParameterizedContext<StateT = DefaultState, ContextT = DefaultContext, ResponseBodyT = unknown> =
731 & ExtendableContext
732 & { state: StateT }
733 & ContextT
734 & { body: ResponseBodyT; response: { body: ResponseBodyT } };
735
736 interface Context extends ParameterizedContext {}
737
738 type Next = () => Promise<any>;
739
740 /**
741 * A re-export of `HttpError` from the `http-error` package.
742 *
743 * This is the error type that is thrown by `ctx.assert()` and `ctx.throw()`.
744 */
745 const HttpError: typeof HttpErrors.HttpError;
746}
747
748export = Application;