diff --git a/attw.json b/attw.json index 2cbbaab7d85e9d..639018244df1ef 100644 --- a/attw.json +++ b/attw.json @@ -164,6 +164,7 @@ "katex", "keep-network__tbtc.js", "koa", + "koa/v2", "kss", "leveldown", "locutus", diff --git a/types/koa/.npmignore b/types/koa/.npmignore index 93e307400a5456..d73cba2e3a91b8 100644 --- a/types/koa/.npmignore +++ b/types/koa/.npmignore @@ -3,3 +3,4 @@ !**/*.d.cts !**/*.d.mts !**/*.d.*.ts +/v2/ diff --git a/types/koa/v2/.eslintrc.json b/types/koa/v2/.eslintrc.json new file mode 100644 index 00000000000000..c16f75a689eed8 --- /dev/null +++ b/types/koa/v2/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "rules": { + "@definitelytyped/no-unnecessary-generics": "off", + "@definitelytyped/strict-export-declare-modifiers": "off", + "@typescript-eslint/no-unsafe-function-type": "off", + "@typescript-eslint/no-wrapper-object-types": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/consistent-type-definitions": "off" + } +} diff --git a/types/koa/v2/.npmignore b/types/koa/v2/.npmignore new file mode 100644 index 00000000000000..93e307400a5456 --- /dev/null +++ b/types/koa/v2/.npmignore @@ -0,0 +1,5 @@ +* +!**/*.d.ts +!**/*.d.cts +!**/*.d.mts +!**/*.d.*.ts diff --git a/types/koa/v2/index.d.ts b/types/koa/v2/index.d.ts new file mode 100644 index 00000000000000..d3cb0d11917634 --- /dev/null +++ b/types/koa/v2/index.d.ts @@ -0,0 +1,748 @@ +/* =================== USAGE =================== + + import * as Koa from "koa" + const app = new Koa() + + async function (ctx: Koa.Context, next: Koa.Next) { + // ... + } + + =============================================== */ +/// +import accepts = require("accepts"); +import { AsyncLocalStorage } from "async_hooks"; +import Cookies = require("cookies"); +import { EventEmitter } from "events"; +import { IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders, Server, ServerResponse } from "http"; +import { Http2ServerRequest, Http2ServerResponse } from "http2"; +import httpAssert = require("http-assert"); +import contentDisposition = require("content-disposition"); +import HttpErrors = require("http-errors"); +import Keygrip = require("keygrip"); +import compose = require("koa-compose"); +import { ListenOptions, Socket } from "net"; +import { ParsedUrlQuery } from "querystring"; +import * as url from "url"; + +declare interface ContextDelegatedRequest { + /** + * Return request header. + */ + header: IncomingHttpHeaders; + + /** + * Return request header, alias as request.header + */ + headers: IncomingHttpHeaders; + + /** + * Get/Set request URL. + */ + url: string; + + /** + * Get origin of URL. + */ + origin: string; + + /** + * Get full request URL. + */ + href: string; + + /** + * Get/Set request method. + */ + method: string; + + /** + * Get request pathname. + * Set pathname, retaining the query-string when present. + */ + path: string; + + /** + * Get parsed query-string. + * Set query-string as an object. + */ + query: ParsedUrlQuery; + + /** + * Get/Set query string. + */ + querystring: string; + + /** + * Get the search string. Same as the querystring + * except it includes the leading ?. + * + * Set the search string. Same as + * response.querystring= but included for ubiquity. + */ + search: string; + + /** + * Parse the "Host" header field host + * and support X-Forwarded-Host when a + * proxy is enabled. + */ + host: string; + + /** + * Parse the "Host" header field hostname + * and support X-Forwarded-Host when a + * proxy is enabled. + */ + hostname: string; + + /** + * Get WHATWG parsed URL object. + */ + URL: url.URL; + + /** + * Check if the request is fresh, aka + * Last-Modified and/or the ETag + * still match. + */ + fresh: boolean; + + /** + * Check if the request is stale, aka + * "Last-Modified" and / or the "ETag" for the + * resource has changed. + */ + stale: boolean; + + /** + * Check if the request is idempotent. + */ + idempotent: boolean; + + /** + * Return the request socket. + */ + socket: Socket; + + /** + * Return the protocol string "http" or "https" + * when requested with TLS. When the proxy setting + * is enabled the "X-Forwarded-Proto" header + * field will be trusted. If you're running behind + * a reverse proxy that supplies https for you this + * may be enabled. + */ + protocol: string; + + /** + * Short-hand for: + * + * this.protocol == 'https' + */ + secure: boolean; + + /** + * Request remote address. Supports X-Forwarded-For when app.proxy is true. + */ + ip: string; + + /** + * When `app.proxy` is `true`, parse + * the "X-Forwarded-For" ip address list. + * + * For example if the value were "client, proxy1, proxy2" + * you would receive the array `["client", "proxy1", "proxy2"]` + * where "proxy2" is the furthest down-stream. + */ + ips: string[]; + + /** + * Return subdomains as an array. + * + * Subdomains are the dot-separated parts of the host before the main domain + * of the app. By default, the domain of the app is assumed to be the last two + * parts of the host. This can be changed by setting `app.subdomainOffset`. + * + * For example, if the domain is "tobi.ferrets.example.com": + * If `app.subdomainOffset` is not set, this.subdomains is + * `["ferrets", "tobi"]`. + * If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`. + */ + subdomains: string[]; + + /** + * Check if the given `type(s)` is acceptable, returning + * the best match when true, otherwise `false`, in which + * case you should respond with 406 "Not Acceptable". + * + * The `type` value may be a single mime type string + * such as "application/json", the extension name + * such as "json" or an array `["json", "html", "text/plain"]`. When a list + * or array is given the _best_ match, if any is returned. + * + * Examples: + * + * // Accept: text/html + * this.accepts('html'); + * // => "html" + * + * // Accept: text/*, application/json + * this.accepts('html'); + * // => "html" + * this.accepts('text/html'); + * // => "text/html" + * this.accepts('json', 'text'); + * // => "json" + * this.accepts('application/json'); + * // => "application/json" + * + * // Accept: text/*, application/json + * this.accepts('image/png'); + * this.accepts('png'); + * // => undefined + * + * // Accept: text/*;q=.5, application/json + * this.accepts(['html', 'json']); + * this.accepts('html', 'json'); + * // => "json" + */ + accepts(): string[]; + accepts(...types: string[]): string | false; + accepts(types: string[]): string | false; + + /** + * Return accepted encodings or best fit based on `encodings`. + * + * Given `Accept-Encoding: gzip, deflate` + * an array sorted by quality is returned: + * + * ['gzip', 'deflate'] + */ + acceptsEncodings(): string[]; + acceptsEncodings(...encodings: string[]): string | false; + acceptsEncodings(encodings: string[]): string | false; + + /** + * Return accepted charsets or best fit based on `charsets`. + * + * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5` + * an array sorted by quality is returned: + * + * ['utf-8', 'utf-7', 'iso-8859-1'] + */ + acceptsCharsets(): string[]; + acceptsCharsets(...charsets: string[]): string | false; + acceptsCharsets(charsets: string[]): string | false; + + /** + * Return accepted languages or best fit based on `langs`. + * + * Given `Accept-Language: en;q=0.8, es, pt` + * an array sorted by quality is returned: + * + * ['es', 'pt', 'en'] + */ + acceptsLanguages(): string[]; + acceptsLanguages(...langs: string[]): string | false; + acceptsLanguages(langs: string[]): string | false; + + /** + * Check if the incoming request contains the "Content-Type" + * header field, and it contains any of the give mime `type`s. + * If there is no request body, `null` is returned. + * If there is no content type, `false` is returned. + * Otherwise, it returns the first `type` that matches. + * + * Examples: + * + * // With Content-Type: text/html; charset=utf-8 + * this.is('html'); // => 'html' + * this.is('text/html'); // => 'text/html' + * this.is('text/*', 'application/json'); // => 'text/html' + * + * // When Content-Type is application/json + * this.is('json', 'urlencoded'); // => 'json' + * this.is('application/json'); // => 'application/json' + * this.is('html', 'application/*'); // => 'application/json' + * + * this.is('html'); // => false + */ + // is(): string | boolean; + is(...types: string[]): string | false | null; + is(types: string[]): string | false | null; + + /** + * Return request header. If the header is not set, will return an empty + * string. + * + * The `Referrer` header field is special-cased, both `Referrer` and + * `Referer` are interchangeable. + * + * Examples: + * + * this.get('Content-Type'); + * // => "text/plain" + * + * this.get('content-type'); + * // => "text/plain" + * + * this.get('Something'); + * // => '' + */ + get(field: string): string; +} + +declare interface ContextDelegatedResponse { + /** + * Get/Set response status code. + */ + status: number; + + /** + * Get response status message + */ + message: string; + + /** + * Get/Set response body. + */ + body: unknown; + + /** + * Return parsed response Content-Length when present. + * Set Content-Length field to `n`. + */ + length: number; + + /** + * Check if a header has been written to the socket. + */ + headerSent: boolean; + + /** + * Vary on `field`. + */ + vary(field: string | string[]): void; + + /** + * Perform a 302 redirect to `url`. + * + * The string "back" is special-cased + * to provide Referrer support, when Referrer + * is not present `alt` or "/" is used. + * + * Examples: + * + * this.redirect('back'); + * this.redirect('back', '/index.html'); + * this.redirect('/login'); + * this.redirect('http://google.com'); + */ + redirect(url: string, alt?: string): void; + + /** + * Set Content-Disposition to "attachment" to signal the client to prompt for download. + * Optionally specify the filename of the download and some options. + */ + attachment(filename?: string, options?: contentDisposition.Options): void; + + /** + * Return the response mime type void of + * parameters such as "charset". + * + * Set Content-Type response header with `type` through `mime.lookup()` + * when it does not contain a charset. + * + * Examples: + * + * this.type = '.html'; + * this.type = 'html'; + * this.type = 'json'; + * this.type = 'application/json'; + * this.type = 'png'; + */ + type: string; + + /** + * Get the Last-Modified date in Date form, if it exists. + * Set the Last-Modified date using a string or a Date. + * + * this.response.lastModified = new Date(); + * this.response.lastModified = '2013-09-13'; + */ + lastModified: Date; + + /** + * Get/Set the ETag of a response. + * This will normalize the quotes if necessary. + * + * this.response.etag = 'md5hashsum'; + * this.response.etag = '"md5hashsum"'; + * this.response.etag = 'W/"123456789"'; + * + * @param {String} etag + * @api public + */ + etag: string; + + /** + * Set header `field` to `val`, or pass + * an object of header fields. + * + * Examples: + * + * this.set('Foo', ['bar', 'baz']); + * this.set('Accept', 'application/json'); + * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); + */ + set(field: { [key: string]: string | string[] }): void; + set(field: string, val: string | string[]): void; + + /** + * Append additional header `field` with value `val`. + * + * Examples: + * + * ``` + * this.append('Link', ['', '']); + * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly'); + * this.append('Warning', '199 Miscellaneous warning'); + * ``` + */ + append(field: string, val: string | string[]): void; + + /** + * Remove header `field`. + */ + remove(field: string): void; + + /** + * Checks if the request is writable. + * Tests for the existence of the socket + * as node sometimes does not set it. + */ + writable: boolean; + + /** + * Flush any set headers, and begin the body + */ + flushHeaders(): void; +} + +declare class Application< + StateT = Application.DefaultState, + ContextT = Application.DefaultContext, +> extends EventEmitter { + proxy: boolean; + proxyIpHeader: string; + maxIpsCount: number; + middleware: Array>; + subdomainOffset: number; + env: string; + context: Application.BaseContext & ContextT; + request: Application.BaseRequest; + response: Application.BaseResponse; + silent: boolean; + keys: Keygrip | string[]; + ctxStorage: AsyncLocalStorage | undefined; + + /** + * @param {object} [options] Application options + * @param {string} [options.env='development'] Environment + * @param {string[]} [options.keys] Signed cookie keys + * @param {boolean} [options.proxy] Trust proxy headers + * @param {number} [options.subdomainOffset] Subdomain offset + * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For + * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity) + * @param {boolean} [options.asyncLocalStorage] Enable AsyncLocalStorage + */ + constructor(options?: { + env?: string | undefined; + keys?: string[] | undefined; + proxy?: boolean | undefined; + subdomainOffset?: number | undefined; + proxyIpHeader?: string | undefined; + maxIpsCount?: number | undefined; + asyncLocalStorage?: boolean | undefined; + }); + + /** + * Shorthand for: + * + * http.createServer(app.callback()).listen(...) + */ + listen(port?: number, hostname?: string, backlog?: number, listeningListener?: () => void): Server; + listen(port: number, hostname?: string, listeningListener?: () => void): Server; + listen(port: number, backlog?: number, listeningListener?: () => void): Server; + listen(port: number, listeningListener?: () => void): Server; + listen(path: string, backlog?: number, listeningListener?: () => void): Server; + listen(path: string, listeningListener?: () => void): Server; + listen(options: ListenOptions, listeningListener?: () => void): Server; + listen(handle: any, backlog?: number, listeningListener?: () => void): Server; + listen(handle: any, listeningListener?: () => void): Server; + + /** + * Return JSON representation. + * We only bother showing settings. + */ + inspect(): any; + + /** + * Return JSON representation. + * We only bother showing settings. + */ + toJSON(): any; + + /** + * Use the given middleware `fn`. + * + * Old-style middleware will be converted. + */ + use( + middleware: Application.Middleware, + ): Application; + + /** + * Return a request handler callback + * for node's native http/http2 server. + */ + callback(): (req: IncomingMessage | Http2ServerRequest, res: ServerResponse | Http2ServerResponse) => Promise; + + /** + * Initialize a new context. + * + * @api private + */ + createContext( + req: IncomingMessage, + res: ServerResponse, + ): Application.ParameterizedContext; + + /** + * Default error handler. + * + * @api private + */ + onerror(err: Error): void; + + /** + * return current context from async local storage + */ + readonly currentContext: ContextT | undefined; +} + +declare namespace Application { + type DefaultStateExtends = any; + /** + * This interface can be augmented by users to add types to Koa's default state + */ + interface DefaultState extends DefaultStateExtends {} + + type DefaultContextExtends = {}; + /** + * This interface can be augmented by users to add types to Koa's default context + */ + interface DefaultContext extends DefaultContextExtends { + /** + * Custom properties. + */ + [key: PropertyKey]: any; + } + + type Middleware = compose.Middleware< + ParameterizedContext + >; + + interface BaseRequest extends ContextDelegatedRequest { + /** + * Get the charset when present or undefined. + */ + charset: string; + + /** + * Return parsed Content-Length when present. + */ + length: number; + + /** + * Return the request mime type void of + * parameters such as "charset". + */ + type: string; + + /** + * Inspect implementation. + */ + inspect(): any; + + /** + * Return JSON representation. + */ + toJSON(): any; + } + + interface BaseResponse extends ContextDelegatedResponse { + /** + * Return the request socket. + * + * @return {Connection} + * @api public + */ + socket: Socket; + + /** + * Return response header. + */ + header: OutgoingHttpHeaders; + + /** + * Return response header, alias as response.header + */ + headers: OutgoingHttpHeaders; + + /** + * Check whether the response is one of the listed types. + * Pretty much the same as `this.request.is()`. + * + * @param {String|Array} types... + * @return {String|false} + * @api public + */ + // is(): string; + is(...types: string[]): string | false | null; + is(types: string[]): string | false | null; + + /** + * Return response header. If the header is not set, will return an empty + * string. + * + * The `Referrer` header field is special-cased, both `Referrer` and + * `Referer` are interchangeable. + * + * Examples: + * + * this.get('Content-Type'); + * // => "text/plain" + * + * this.get('content-type'); + * // => "text/plain" + * + * this.get('Something'); + * // => '' + */ + get(field: string): string; + + /** + * Inspect implementation. + */ + inspect(): any; + + /** + * Return JSON representation. + */ + toJSON(): any; + } + + interface BaseContext extends ContextDelegatedRequest, ContextDelegatedResponse { + /** + * util.inspect() implementation, which + * just returns the JSON output. + */ + inspect(): any; + + /** + * Return JSON representation. + * + * Here we explicitly invoke .toJSON() on each + * object, as iteration will otherwise fail due + * to the getters and cause utilities such as + * clone() to fail. + */ + toJSON(): any; + + /** + * Similar to .throw(), adds assertion. + * + * this.assert(this.user, 401, 'Please login!'); + * + * See: https://github.com/jshttp/http-assert + */ + assert: typeof httpAssert; + + /** + * Throw an error with `msg` and optional `status` + * defaulting to 500. Note that these are user-level + * errors, and the message may be exposed to the client. + * + * this.throw(403) + * this.throw('name required', 400) + * this.throw(400, 'name required') + * this.throw('something exploded') + * this.throw(new Error('invalid'), 400); + * this.throw(400, new Error('invalid')); + * + * See: https://github.com/jshttp/http-errors + */ + throw(message: string, code?: number, properties?: {}): never; + throw(status: number): never; + throw(...properties: Array): never; + + /** + * Default error handling. + */ + onerror(err: Error): void; + } + + interface Request extends BaseRequest { + app: Application; + req: IncomingMessage; + res: ServerResponse; + ctx: Context; + response: Response; + originalUrl: string; + ip: string; + accept: accepts.Accepts; + } + + interface Response extends BaseResponse { + app: Application; + req: IncomingMessage; + res: ServerResponse; + ctx: Context; + request: Request; + } + + interface ExtendableContext extends BaseContext { + app: Application; + request: Request; + response: Response; + req: IncomingMessage; + res: ServerResponse; + originalUrl: string; + cookies: Cookies; + accept: accepts.Accepts; + /** + * To bypass Koa's built-in response handling, you may explicitly set `ctx.respond = false;` + */ + respond?: boolean | undefined; + } + + type ParameterizedContext = + & ExtendableContext + & { state: StateT } + & ContextT + & { body: ResponseBodyT; response: { body: ResponseBodyT } }; + + interface Context extends ParameterizedContext {} + + type Next = () => Promise; + + /** + * A re-export of `HttpError` from the `http-error` package. + * + * This is the error type that is thrown by `ctx.assert()` and `ctx.throw()`. + */ + const HttpError: typeof HttpErrors.HttpError; +} + +export = Application; diff --git a/types/koa/v2/package.json b/types/koa/v2/package.json new file mode 100644 index 00000000000000..57dcf0a61596c3 --- /dev/null +++ b/types/koa/v2/package.json @@ -0,0 +1,51 @@ +{ + "private": true, + "name": "@types/koa", + "version": "2.15.9999", + "projects": [ + "http://koajs.com" + ], + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + }, + "devDependencies": { + "@types/koa": "workspace:." + }, + "owners": [ + { + "name": "jKey Lu", + "githubUsername": "jkeylu" + }, + { + "name": "Brice Bernard", + "githubUsername": "brikou" + }, + { + "name": "harryparkdotio", + "githubUsername": "harryparkdotio" + }, + { + "name": "Wooram Jun", + "githubUsername": "chatoo2412" + }, + { + "name": "Christian Vaagland Tellnes", + "githubUsername": "tellnes" + }, + { + "name": "Piotr Kuczynski", + "githubUsername": "pkuczynski" + }, + { + "name": "vnoder", + "githubUsername": "vnoder" + } + ] +} diff --git a/types/koa/v2/test/constructor.ts b/types/koa/v2/test/constructor.ts new file mode 100644 index 00000000000000..eb3c7debc44b04 --- /dev/null +++ b/types/koa/v2/test/constructor.ts @@ -0,0 +1,19 @@ +import Koa = require("koa"); + +const app = new Koa({ + env: "abc", + keys: ["im a newer secret", "i like turtle"], + proxy: true, + subdomainOffset: 2, + proxyIpHeader: "XYZ-Forwarded-For", + maxIpsCount: 2, + asyncLocalStorage: true, +}); + +app.use(ctx => { + ctx.body = "Hello World"; +}); + +app.listen(3000); + +const server = app.listen(); diff --git a/types/koa/v2/test/default.ts b/types/koa/v2/test/default.ts new file mode 100644 index 00000000000000..f9e34b8297eb05 --- /dev/null +++ b/types/koa/v2/test/default.ts @@ -0,0 +1,78 @@ +import Koa = require("koa"); + +declare module "koa" { + interface DefaultState { + stateProperty: boolean; + } + + interface DefaultContext { + logger: { + info: Function; + log: Function; + error: Function; + }; + } +} + +const app = new Koa(); + +app.context.logger = { + info: () => {}, + log: () => {}, + error: () => {}, +}; + +app.use((ctx, next) => { + ctx.state.stateProperty = false; + return next(); +}); + +app.use<{ a: boolean }>(async (ctx, next) => { + ctx.state.a = true; + ctx.state.b = ""; // undeclared property + await next(); +}); + +app.use((ctx: Koa.Context, next) => { + const start: any = new Date(); + return next().then(() => { + const end: any = new Date(); + const ms = end - start; + ctx.logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`); + ctx.user = {}; // undeclared property + ctx["views"] = 123; // string property key + ctx["views"]; // $ExpectType any + ctx[200] = {}; // number property key + ctx[200]; // $ExpectType any + ctx[Symbol("locale")] = "en-US"; // symbol property key + ctx[Symbol("locale")]; // $ExpectType any + ctx.assert(true, 404, "Yep!"); + }); +}); + +// response +app.use(ctx => { + ctx.body = "Hello World"; + ctx.body = ctx.URL.toString(); + ctx.set({ + link: ["", ""], + }); + ctx.attachment(); + ctx.attachment("path/to/tobi.png"); + ctx.attachment("path/to/tobi.png", { + type: "inline", + }); +}); + +app.on("error", error => { + if (error instanceof Koa.HttpError) { + // $ExpectType number + error.status; + throw error; + } + throw error; +}); + +app.listen(3000); + +const server = app.listen(); diff --git a/types/koa/v2/test/index.ts b/types/koa/v2/test/index.ts new file mode 100644 index 00000000000000..5e64ec35b50455 --- /dev/null +++ b/types/koa/v2/test/index.ts @@ -0,0 +1,88 @@ +import Koa = require("koa"); + +declare module "koa" { + interface ExtendableContext { + errors?: Error[] | undefined; + } +} + +interface DbBaseContext { + db(): void; +} + +interface UserContext { + user: {}; +} + +const app = new Koa<{}, DbBaseContext>(); + +app.context.db = () => {}; + +app.use(async ctx => { + if (ctx.errors) { + ctx.throw(400, ctx.errors[0]); + ctx.throw(400); + ctx.throw(400, "name required"); + ctx.throw(400, "name required", { user: { id: 7 } }); + ctx.throw(403); + ctx.throw(400, "name required"); + ctx.throw("something exploded"); + ctx.throw(new Error("invalid")); + ctx.throw(400, new Error("invalid")); + } +}); + +app.use(async (ctx, next) => { + try { + return await next(); + } catch (ex: any) { + ctx.errors = [ex]; + } +}); + +app.use<{}, UserContext>(async ctx => { + console.log(ctx.db); + ctx.user = {}; +}); + +app.use((ctx: Koa.Context, next) => { + const start: any = new Date(); + return next().then(() => { + const end: any = new Date(); + const ms = end - start; + console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); + ctx.assert(true, 404, "Yep!"); + }); +}); + +app.use(ctx => { + ctx.accepts(); // $ExpectType string[] + ctx.accepts(""); // $ExpectType string | false + ctx.accepts([""]); // $ExpectType string | false + ctx.acceptsEncodings(); // $ExpectType string[] + ctx.acceptsEncodings(""); // $ExpectType string | false + ctx.acceptsEncodings([""]); // $ExpectType string | false + ctx.acceptsCharsets(); // $ExpectType string[] + ctx.acceptsCharsets(""); // $ExpectType string | false + ctx.acceptsCharsets([""]); // $ExpectType string | false + ctx.acceptsLanguages(); // $ExpectType string[] + ctx.acceptsLanguages(""); // $ExpectType string | false + ctx.acceptsLanguages([""]); // $ExpectType string | false + ctx.is(""); // $ExpectType string | false | null + ctx.is([""]); // $ExpectType string | false | null + + ctx.vary("Origin"); // $ExpectType void + ctx.vary(["Origin", "User-Agent"]); // $ExpectType void +}); + +// response +app.use(ctx => { + ctx.body = "Hello World"; + ctx.body = ctx.URL.toString(); +}); + +app.listen(3000); + +const currentContext: DbBaseContext | undefined = app.currentContext; + +const server = app.listen(); diff --git a/types/koa/v2/test/settings.ts b/types/koa/v2/test/settings.ts new file mode 100644 index 00000000000000..55eb07390779f3 --- /dev/null +++ b/types/koa/v2/test/settings.ts @@ -0,0 +1,25 @@ +// Tests for application settings. https://github.com/koajs/koa/blob/master/docs/api/index.md#settings + +import Koa = require("koa"); + +const app = new Koa(); + +app.env = "development"; + +app.keys = ["im a newer secret", "i like turtle"]; + +app.proxy = true; + +app.subdomainOffset = 2; + +app.proxyIpHeader = "X-Forwarded-For"; + +app.maxIpsCount = 0; + +app.use(ctx => { + ctx.body = "Hello World"; +}); + +app.listen(3000); + +const server = app.listen(); diff --git a/types/koa/v2/test/typed-response-body.ts b/types/koa/v2/test/typed-response-body.ts new file mode 100644 index 00000000000000..05e5b77abff9dd --- /dev/null +++ b/types/koa/v2/test/typed-response-body.ts @@ -0,0 +1,28 @@ +import Koa = require("koa"); + +const app = new Koa(); + +interface CustomResponseBody { + a: number; + b: string; +} + +const middleware: Koa.Middleware = (ctx, next) => { + ctx.body.a = 1; + ctx.body.b = "text"; + + ctx.response.body = { + a: 1, + b: "text", + // @ts-expect-error + c: true, + }; + + return next(); +}; + +app.use(middleware); + +app.listen(3000); + +const server = app.listen(); diff --git a/types/koa/v2/tsconfig.json b/types/koa/v2/tsconfig.json new file mode 100644 index 00000000000000..aa8f83fee67449 --- /dev/null +++ b/types/koa/v2/tsconfig.json @@ -0,0 +1,23 @@ +{ + "files": [ + "index.d.ts", + "test/index.ts", + "test/constructor.ts", + "test/default.ts", + "test/settings.ts", + "test/typed-response-body.ts" + ], + "compilerOptions": { + "module": "node16", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + } +}