@eggjs/koa 2.20.7 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,16 @@
1
- import { debuglog } from 'node:util';
1
+ import util, { debuglog } from 'node:util';
2
2
  import Emitter from 'node:events';
3
- import util from 'node:util';
4
3
  import Stream from 'node:stream';
5
4
  import http from 'node:http';
6
5
  import type { AsyncLocalStorage } from 'node:async_hooks';
7
6
  import type { IncomingMessage, ServerResponse } from 'node:http';
7
+
8
8
  import { getAsyncLocalStorage } from 'gals';
9
9
  import { isGeneratorFunction } from 'is-type-of';
10
10
  import onFinished from 'on-finished';
11
11
  import statuses from 'statuses';
12
12
  import compose from 'koa-compose';
13
+
13
14
  import { HttpError } from 'http-errors';
14
15
  import { Context } from './context.js';
15
16
  import { Request } from './request.js';
@@ -18,10 +19,13 @@ import type { CustomError, AnyProto } from './types.js';
18
19
 
19
20
  const debug = debuglog('@eggjs/koa/application');
20
21
 
21
- export type ProtoImplClass<T = object> = new(...args: any[]) => T;
22
+ // oxlint-disable-next-line typescript/no-explicit-any
23
+ export type ProtoImplClass<T = object> = new (...args: any[]) => T;
22
24
  export type Next = () => Promise<void>;
23
25
  type _MiddlewareFunc<T> = (ctx: T, next: Next) => Promise<void> | void;
24
- export type MiddlewareFunc<T = Context> = _MiddlewareFunc<T> & { _name?: string };
26
+ export type MiddlewareFunc<T extends Context = Context> = _MiddlewareFunc<T> & {
27
+ _name?: string;
28
+ };
25
29
 
26
30
  /**
27
31
  * Expose `Application` class.
@@ -53,15 +57,15 @@ export class Application extends Emitter {
53
57
 
54
58
  /**
55
59
  * Initialize a new `Application`.
56
- *
57
- * @param {object} [options] Application options
58
- * @param {string} [options.env='development'] Environment
59
- * @param {string[]} [options.keys] Signed cookie keys
60
- * @param {boolean} [options.proxy] Trust proxy headers
61
- * @param {number} [options.subdomainOffset] Subdomain offset
62
- * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
63
- * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
64
- */
60
+ *
61
+ * @param {object} [options] Application options
62
+ * @param {string} [options.env] Environment, default is `development`
63
+ * @param {string[]} [options.keys] Signed cookie keys
64
+ * @param {boolean} [options.proxy] Trust proxy headers
65
+ * @param {number} [options.subdomainOffset] Subdomain offset
66
+ * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
67
+ * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
68
+ */
65
69
 
66
70
  constructor(options?: {
67
71
  proxy?: boolean;
@@ -84,11 +88,14 @@ export class Application extends Emitter {
84
88
  this.middleware = [];
85
89
  this.ctxStorage = getAsyncLocalStorage();
86
90
  this.silent = false;
87
- this.ContextClass = class ApplicationContext extends Context {} as ProtoImplClass<Context>;
91
+ this.ContextClass =
92
+ class ApplicationContext extends Context {} as ProtoImplClass<Context>;
88
93
  this.context = this.ContextClass.prototype;
89
- this.RequestClass = class ApplicationRequest extends Request {} as ProtoImplClass<Request>;
94
+ this.RequestClass =
95
+ class ApplicationRequest extends Request {} as ProtoImplClass<Request>;
90
96
  this.request = this.RequestClass.prototype;
91
- this.ResponseClass = class ApplicationResponse extends Response {} as ProtoImplClass<Response>;
97
+ this.ResponseClass =
98
+ class ApplicationResponse extends Response {} as ProtoImplClass<Response>;
92
99
  this.response = this.ResponseClass.prototype;
93
100
  }
94
101
 
@@ -119,6 +126,7 @@ export class Application extends Emitter {
119
126
  *
120
127
  * http.createServer(app.callback()).listen(...)
121
128
  */
129
+ // oxlint-disable-next-line typescript/no-explicit-any
122
130
  listen(...args: any[]) {
123
131
  debug('listen with args: %o', args);
124
132
  const server = http.createServer(this.callback());
@@ -151,16 +159,19 @@ export class Application extends Emitter {
151
159
  /**
152
160
  * Use the given middleware `fn`.
153
161
  */
154
- use(fn: MiddlewareFunc) {
155
- if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
162
+ use<T extends Context = Context>(fn: MiddlewareFunc<T>) {
163
+ if (typeof fn !== 'function')
164
+ throw new TypeError('middleware must be a function!');
156
165
  const name = fn._name || fn.name || '-';
157
166
  if (isGeneratorFunction(fn)) {
158
- throw new TypeError(`Support for generators was removed, middleware: ${name}. ` +
159
- 'See the documentation for examples of how to convert old middleware ' +
160
- 'https://github.com/koajs/koa/blob/master/docs/migration.md');
167
+ throw new TypeError(
168
+ `Support for generators was removed, middleware: ${name}. ` +
169
+ 'See the documentation for examples of how to convert old middleware ' +
170
+ 'https://github.com/koajs/koa/blob/master/docs/migration.md'
171
+ );
161
172
  }
162
173
  debug('use %o #%d', name, this.middleware.length);
163
- this.middleware.push(fn);
174
+ this.middleware.push(fn as MiddlewareFunc<Context>);
164
175
  return this;
165
176
  }
166
177
 
@@ -196,12 +207,16 @@ export class Application extends Emitter {
196
207
  * Handle request in callback.
197
208
  * @private
198
209
  */
199
- protected async handleRequest(ctx: Context, fnMiddleware: (ctx: Context) => Promise<void>) {
210
+ protected async handleRequest(
211
+ ctx: Context,
212
+ fnMiddleware: (ctx: Context) => Promise<void>
213
+ ) {
200
214
  this.emit('request', ctx);
201
215
  const res = ctx.res;
202
216
  res.statusCode = 404;
203
- const onerror = (err: any) => ctx.onerror(err);
204
- onFinished(res, (err: any) => {
217
+ const onerror = (err: CustomError) => ctx.onerror(err);
218
+ // oxlint-disable-next-line promise/prefer-await-to-callbacks
219
+ onFinished(res, (err: CustomError | null) => {
205
220
  if (err) {
206
221
  onerror(err);
207
222
  }
@@ -211,7 +226,7 @@ export class Application extends Emitter {
211
226
  await fnMiddleware(ctx);
212
227
  return this._respond(ctx);
213
228
  } catch (err) {
214
- return onerror(err);
229
+ return onerror(err as CustomError);
215
230
  }
216
231
  }
217
232
 
@@ -232,15 +247,18 @@ export class Application extends Emitter {
232
247
  // When dealing with cross-globals a normal `instanceof` check doesn't work properly.
233
248
  // See https://github.com/koajs/koa/issues/1466
234
249
  // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
235
- const isNativeError = err instanceof Error ||
250
+ const isNativeError =
251
+ err instanceof Error ||
236
252
  Object.prototype.toString.call(err) === '[object Error]';
237
- if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));
253
+ if (!isNativeError)
254
+ throw new TypeError(util.format('non-error thrown: %j', err));
238
255
 
239
256
  if (err.status === 404 || err.expose) return;
240
257
  if (this.silent) return;
241
258
 
242
259
  const msg = err.stack || err.toString();
243
- console.error(`\n${msg.replace(/^/gm, ' ')}\n`);
260
+ // oxlint-disable-next-line no-console
261
+ console.error(`\n${msg.replaceAll(/^/gm, ' ')}\n`);
244
262
  }
245
263
 
246
264
  /**
@@ -272,7 +290,7 @@ export class Application extends Emitter {
272
290
  }
273
291
 
274
292
  // status body
275
- if (body == null) {
293
+ if (body === null || body === undefined) {
276
294
  if (ctx.response._explicitNullBody) {
277
295
  ctx.response.remove('Content-Type');
278
296
  ctx.response.remove('Transfer-Encoding');
package/src/context.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import util from 'node:util';
2
2
  import type { IncomingMessage, ServerResponse } from 'node:http';
3
- import { ParsedUrlQuery } from 'node:querystring';
3
+ import type { ParsedUrlQuery } from 'node:querystring';
4
+
4
5
  import createError from 'http-errors';
5
- import httpAssert from 'http-assert';
6
6
  import statuses from 'statuses';
7
7
  import Cookies from 'cookies';
8
- import { type Accepts } from 'accepts';
8
+ import type { Accepts } from 'accepts';
9
+
9
10
  import type { Application } from './application.js';
10
11
  import type { Request } from './request.js';
11
12
  import type { Response } from './response.js';
@@ -20,6 +21,7 @@ export class Context {
20
21
  response: Response & AnyProto;
21
22
  originalUrl: string;
22
23
  respond?: boolean;
24
+ // oxlint-disable-next-line typescript/no-explicit-any
23
25
  #state: Record<string, any> = {};
24
26
 
25
27
  constructor(app: Application, req: IncomingMessage, res: ServerResponse) {
@@ -27,10 +29,10 @@ export class Context {
27
29
  this.req = req;
28
30
  this.res = res;
29
31
  this.request = new app.RequestClass(app, this, req, res);
30
- this.response = new app.ResponseClass(app, this as any, req, res);
32
+ this.response = new app.ResponseClass(app, this, req, res);
31
33
  this.request.response = this.response;
32
34
  this.response.request = this.request;
33
- this.originalUrl = req.url!;
35
+ this.originalUrl = req.url ?? '/';
34
36
  }
35
37
 
36
38
  /**
@@ -72,20 +74,37 @@ export class Context {
72
74
  /**
73
75
  * Similar to .throw(), adds assertion.
74
76
  *
75
- * this.assert(this.user, 401, 'Please login!');
76
- *
77
- * See: https://github.com/jshttp/http-assert
78
- * @param {Mixed} value
79
- * @param {Number} status
80
- * @param {String} opts
77
+ * ```ts
78
+ * this.assert(this.user, 401, 'Please login!');
79
+ * ```
81
80
  */
82
- assert(value: any, status?: number, opts?: Record<string, any>): void;
83
- assert(value: any, status?: number, msg?: string, opts?: Record<string, any>): void;
84
- assert(value: any, status?: number, msgOrOptions?: string | Record<string, any>, opts?: Record<string, any>) {
85
- if (typeof msgOrOptions === 'string') {
86
- return httpAssert(value, status, msgOrOptions, opts);
81
+ assert(
82
+ value: unknown,
83
+ status?: number,
84
+ errorProps?: Record<string, unknown>
85
+ ): void;
86
+ assert(
87
+ value: unknown,
88
+ status?: number,
89
+ errorMessage?: string,
90
+ errorProps?: Record<string, unknown>
91
+ ): void;
92
+ assert(
93
+ value: unknown,
94
+ status?: number,
95
+ errorMessageOrProps?: string | Record<string, unknown>,
96
+ errorProps?: Record<string, unknown>
97
+ ) {
98
+ if (value) {
99
+ return;
100
+ }
101
+ status = status ?? 500;
102
+ if (typeof errorMessageOrProps === 'string') {
103
+ // assert(value, status, errorMessage, errorProps?)
104
+ throw createError(status, errorMessageOrProps, errorProps ?? {});
87
105
  }
88
- return httpAssert(value, status, msgOrOptions);
106
+ // assert(value, status, errorProps?)
107
+ throw createError(status, errorMessageOrProps ?? {});
89
108
  }
90
109
 
91
110
  /**
@@ -124,7 +143,12 @@ export class Context {
124
143
  throw(error: Error, errorProps: object): void;
125
144
  throw(error: Error, status: number): void;
126
145
  throw(error: Error, status: number, errorProps: object): void;
127
- throw(arg1: number | string | Error, arg2?: number | string | Error | object, errorProps?: object) {
146
+ throw(
147
+ arg1: number | string | Error,
148
+ arg2?: number | string | Error | object,
149
+ errorProps?: object
150
+ ) {
151
+ // oxlint-disable-next-line typescript/no-explicit-any
128
152
  const args: any[] = [];
129
153
  if (typeof arg2 === 'number') {
130
154
  // throw(error, status)
@@ -156,7 +180,8 @@ export class Context {
156
180
  // When dealing with cross-globals a normal `instanceof` check doesn't work properly.
157
181
  // See https://github.com/koajs/koa/issues/1466
158
182
  // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
159
- const isNativeError = err instanceof Error ||
183
+ const isNativeError =
184
+ err instanceof Error ||
160
185
  Object.prototype.toString.call(err) === '[object Error]';
161
186
  if (!isNativeError) {
162
187
  err = new Error(util.format('non-error thrown: %j', err));
@@ -164,7 +189,8 @@ export class Context {
164
189
 
165
190
  let headerSent = false;
166
191
  if (this.response.headerSent || !this.response.writable) {
167
- headerSent = (err as any).headerSent = true;
192
+ headerSent = true;
193
+ err.headerSent = true;
168
194
  }
169
195
 
170
196
  // delegate
@@ -180,10 +206,14 @@ export class Context {
180
206
  const { res } = this;
181
207
 
182
208
  // first unset all headers
183
- res.getHeaderNames().forEach(name => res.removeHeader(name));
209
+ for (const name of res.getHeaderNames()) {
210
+ res.removeHeader(name);
211
+ }
184
212
 
185
213
  // then set those specified
186
- if (err.headers) this.response.set(err.headers);
214
+ if (err.headers) {
215
+ this.response.set(err.headers);
216
+ }
187
217
 
188
218
  // force text/plain
189
219
  this.response.type = 'text';
@@ -191,15 +221,20 @@ export class Context {
191
221
  let statusCode = err.status || err.statusCode;
192
222
 
193
223
  // ENOENT support
194
- if (err.code === 'ENOENT') statusCode = 404;
224
+ if (err.code === 'ENOENT') {
225
+ statusCode = 404;
226
+ }
195
227
 
196
228
  // default to 500
197
- if (typeof statusCode !== 'number' || !statuses.message[statusCode]) statusCode = 500;
229
+ if (typeof statusCode !== 'number' || !statuses.message[statusCode]) {
230
+ statusCode = 500;
231
+ }
198
232
 
199
233
  // respond
200
234
  const statusMessage = statuses.message[statusCode] as string;
201
235
  const msg = err.expose ? err.message : statusMessage;
202
- this.response.status = err.status = statusCode;
236
+ err.status = statusCode;
237
+ this.response.status = statusCode;
203
238
  this.response.length = Buffer.byteLength(msg);
204
239
  res.end(msg);
205
240
  }
@@ -231,29 +266,43 @@ export class Context {
231
266
  acceptsLanguages(): string[];
232
267
  acceptsLanguages(languages: string[]): string | false;
233
268
  acceptsLanguages(...languages: string[]): string | false;
234
- acceptsLanguages(languages?: string | string[], ...others: string[]): string | string[] | false {
235
- return this.request.acceptsLanguages(languages as any, ...others);
269
+ acceptsLanguages(
270
+ languages?: string | string[],
271
+ ...others: string[]
272
+ ): string | string[] | false {
273
+ return this.request.acceptsLanguages(languages as string, ...others);
236
274
  }
237
275
 
238
276
  acceptsEncodings(): string[];
239
277
  acceptsEncodings(encodings: string[]): string | false;
240
278
  acceptsEncodings(...encodings: string[]): string | false;
241
- acceptsEncodings(encodings?: string | string[], ...others: string[]): string[] | string | false {
242
- return this.request.acceptsEncodings(encodings as any, ...others);
279
+ acceptsEncodings(
280
+ encodings?: string | string[],
281
+ ...others: string[]
282
+ ): string[] | string | false {
283
+ return this.request.acceptsEncodings(encodings as string, ...others);
243
284
  }
244
285
 
245
286
  acceptsCharsets(): string[];
246
287
  acceptsCharsets(charsets: string[]): string | false;
247
288
  acceptsCharsets(...charsets: string[]): string | false;
248
- acceptsCharsets(charsets?: string | string[], ...others: string[]): string[] | string | false {
249
- return this.request.acceptsCharsets(charsets as any, ...others);
289
+ acceptsCharsets(
290
+ charsets?: string | string[],
291
+ ...others: string[]
292
+ ): string[] | string | false {
293
+ return this.request.acceptsCharsets(charsets as string, ...others);
250
294
  }
251
295
 
252
- accepts(...args: Parameters<Request['accepts']>): string | string[] | false {
253
- return this.request.accepts(...args);
296
+ accepts(args: string[]): string | string[] | false;
297
+ accepts(...args: string[]): string | string[] | false;
298
+ accepts(
299
+ args?: string | string[],
300
+ ...others: string[]
301
+ ): string | string[] | false {
302
+ return this.request.accepts(args as string, ...others);
254
303
  }
255
304
 
256
- get<T = string | string []>(field: string): T {
305
+ get<T = string | string[]>(field: string): T {
257
306
  return this.request.get(field);
258
307
  }
259
308
 
@@ -433,10 +482,12 @@ export class Context {
433
482
  this.response.message = msg;
434
483
  }
435
484
 
485
+ // oxlint-disable-next-line typescript/no-explicit-any
436
486
  get body(): any {
437
487
  return this.response.body;
438
488
  }
439
489
 
490
+ // oxlint-disable-next-line typescript/no-explicit-any
440
491
  set body(val: any) {
441
492
  this.response.body = val;
442
493
  }
package/src/index.ts CHANGED
@@ -6,4 +6,4 @@ export * from './application.js';
6
6
  export * from './context.js';
7
7
  export * from './request.js';
8
8
  export * from './response.js';
9
- export * from './types.js';
9
+ export type { CustomError, AnyProto } from './types.js';