@eggjs/koa 2.21.0 → 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.
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';
package/src/request.ts CHANGED
@@ -3,11 +3,13 @@ import { format as stringify } from 'node:url';
3
3
  import qs, { type ParsedUrlQuery } from 'node:querystring';
4
4
  import util from 'node:util';
5
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
6
+
6
7
  import accepts, { type Accepts } from 'accepts';
7
8
  import contentType from 'content-type';
8
9
  import parse from 'parseurl';
9
10
  import typeis from 'type-is';
10
11
  import fresh from 'fresh';
12
+
11
13
  import type { Application } from './application.js';
12
14
  import type { Context } from './context.js';
13
15
  import type { Response } from './response.js';
@@ -25,12 +27,17 @@ export class Request {
25
27
  response: Response;
26
28
  originalUrl: string;
27
29
 
28
- constructor(app: Application, ctx: Context, req: IncomingMessage, res: ServerResponse) {
30
+ constructor(
31
+ app: Application,
32
+ ctx: Context,
33
+ req: IncomingMessage,
34
+ res: ServerResponse
35
+ ) {
29
36
  this.app = app;
30
37
  this.req = req;
31
38
  this.res = res;
32
39
  this.ctx = ctx;
33
- this.originalUrl = req.url!;
40
+ this.originalUrl = req.url ?? '/';
34
41
  }
35
42
 
36
43
  /**
@@ -70,7 +77,7 @@ export class Request {
70
77
  */
71
78
 
72
79
  get url() {
73
- return this.req.url!;
80
+ return this.req.url ?? '/';
74
81
  }
75
82
 
76
83
  /**
@@ -95,7 +102,9 @@ export class Request {
95
102
 
96
103
  get href() {
97
104
  // support: `GET http://example.com/foo`
98
- if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl;
105
+ if (/^https?:\/\//i.test(this.originalUrl)) {
106
+ return this.originalUrl;
107
+ }
99
108
  return this.origin + this.originalUrl;
100
109
  }
101
110
 
@@ -104,34 +113,32 @@ export class Request {
104
113
  */
105
114
 
106
115
  get method() {
107
- return this.req.method!;
116
+ return this.req.method ?? 'GET';
108
117
  }
109
118
 
110
119
  /**
111
120
  * Set request method.
112
121
  */
113
-
114
- set method(val) {
122
+ set method(val: string) {
115
123
  this.req.method = val;
116
124
  }
117
125
 
118
126
  /**
119
127
  * Get request pathname.
120
128
  */
121
-
122
129
  get path() {
123
- return parse(this.req)!.pathname as string;
130
+ return parse(this.req)?.pathname ?? '';
124
131
  }
125
132
 
126
133
  /**
127
134
  * Set pathname, retaining the query string when present.
128
135
  */
136
+ set path(pathname: string) {
137
+ const url = parse(this.req);
138
+ if (!url) return;
139
+ if (url.pathname === pathname) return;
129
140
 
130
- set path(path) {
131
- const url = parse(this.req)!;
132
- if (url.pathname === path) return;
133
-
134
- url.pathname = path;
141
+ url.pathname = pathname;
135
142
  url.path = null;
136
143
 
137
144
  this.url = stringify(url);
@@ -149,7 +156,8 @@ export class Request {
149
156
  }
150
157
  let parsedUrlQuery = this._parsedUrlQueryCache[str];
151
158
  if (!parsedUrlQuery) {
152
- parsedUrlQuery = this._parsedUrlQueryCache[str] = qs.parse(str);
159
+ parsedUrlQuery = qs.parse(str);
160
+ this._parsedUrlQueryCache[str] = parsedUrlQuery;
153
161
  }
154
162
  return parsedUrlQuery;
155
163
  }
@@ -157,7 +165,6 @@ export class Request {
157
165
  /**
158
166
  * Set query string as an object.
159
167
  */
160
-
161
168
  set query(obj: ParsedUrlQuery) {
162
169
  this.querystring = qs.stringify(obj);
163
170
  }
@@ -165,18 +172,17 @@ export class Request {
165
172
  /**
166
173
  * Get query string.
167
174
  */
168
-
169
175
  get querystring() {
170
176
  if (!this.req) return '';
171
- return parse(this.req)!.query as string || '';
177
+ return (parse(this.req)?.query as string) ?? '';
172
178
  }
173
179
 
174
180
  /**
175
181
  * Set query string.
176
182
  */
177
-
178
- set querystring(str) {
179
- const url = parse(this.req)!;
183
+ set querystring(str: string) {
184
+ const url = parse(this.req);
185
+ if (!url) return;
180
186
  if (url.search === `?${str}`) return;
181
187
 
182
188
  url.search = str;
@@ -188,18 +194,17 @@ export class Request {
188
194
  * Get the search string. Same as the query string
189
195
  * except it includes the leading ?.
190
196
  */
191
-
192
197
  get search() {
193
- if (!this.querystring) return '';
194
- return `?${this.querystring}`;
198
+ const querystring = this.querystring;
199
+ if (!querystring) return '';
200
+ return `?${querystring}`;
195
201
  }
196
202
 
197
203
  /**
198
204
  * Set the search string. Same as
199
205
  * request.querystring= but included for ubiquity.
200
206
  */
201
-
202
- set search(str) {
207
+ set search(str: string) {
203
208
  this.querystring = str;
204
209
  }
205
210
 
@@ -257,7 +262,7 @@ export class Request {
257
262
  this._memoizedURL = Object.create(null);
258
263
  }
259
264
  }
260
- return this._memoizedURL!;
265
+ return this._memoizedURL as URL;
261
266
  }
262
267
 
263
268
  /**
@@ -295,7 +300,7 @@ export class Request {
295
300
  * Check if the request is idempotent.
296
301
  */
297
302
  get idempotent() {
298
- const methods = [ 'GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE' ];
303
+ const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];
299
304
  return methods.includes(this.method);
300
305
  }
301
306
 
@@ -326,7 +331,7 @@ export class Request {
326
331
  if (len === '') {
327
332
  return;
328
333
  }
329
- return parseInt(len);
334
+ return Number.parseInt(len);
330
335
  }
331
336
 
332
337
  /**
@@ -371,9 +376,7 @@ export class Request {
371
376
  get ips() {
372
377
  const proxy = this.app.proxy;
373
378
  const val = this.get<string>(this.app.proxyIpHeader);
374
- let ips = proxy && val
375
- ? splitCommaSeparatedValues(val)
376
- : [];
379
+ let ips = proxy && val ? splitCommaSeparatedValues(val) : [];
377
380
  if (this.app.maxIpsCount > 0) {
378
381
  ips = ips.slice(-this.app.maxIpsCount);
379
382
  }
@@ -413,10 +416,7 @@ export class Request {
413
416
  const offset = this.app.subdomainOffset;
414
417
  const hostname = this.hostname;
415
418
  if (net.isIP(hostname)) return [];
416
- return hostname
417
- .split('.')
418
- .reverse()
419
- .slice(offset);
419
+ return hostname.split('.').reverse().slice(offset);
420
420
  }
421
421
 
422
422
  protected _accept: Accepts;
@@ -471,8 +471,13 @@ export class Request {
471
471
  * this.accepts('html', 'json');
472
472
  * // => "json"
473
473
  */
474
- accepts(...args: any[]): string | string[] | false {
475
- return this.accept.types(...args);
474
+ accepts(args: string[]): string | string[] | false;
475
+ accepts(...args: string[]): string | string[] | false;
476
+ accepts(
477
+ args?: string | string[],
478
+ ...others: string[]
479
+ ): string | string[] | false {
480
+ return this.accept.types(args as string, ...others);
476
481
  }
477
482
 
478
483
  /**
@@ -486,14 +491,17 @@ export class Request {
486
491
  acceptsEncodings(): string[];
487
492
  acceptsEncodings(encodings: string[]): string | false;
488
493
  acceptsEncodings(...encodings: string[]): string | false;
489
- acceptsEncodings(encodings?: string | string[], ...others: string[]): string[] | string | false {
494
+ acceptsEncodings(
495
+ encodings?: string | string[],
496
+ ...others: string[]
497
+ ): string[] | string | false {
490
498
  if (!encodings) {
491
499
  return this.accept.encodings();
492
500
  }
493
501
  if (Array.isArray(encodings)) {
494
- encodings = [ ...encodings, ...others ];
502
+ encodings = [...encodings, ...others];
495
503
  } else {
496
- encodings = [ encodings, ...others ];
504
+ encodings = [encodings, ...others];
497
505
  }
498
506
  return this.accept.encodings(...encodings);
499
507
  }
@@ -509,14 +517,17 @@ export class Request {
509
517
  acceptsCharsets(): string[];
510
518
  acceptsCharsets(charsets: string[]): string | false;
511
519
  acceptsCharsets(...charsets: string[]): string | false;
512
- acceptsCharsets(charsets?: string | string[], ...others: string[]): string[] | string | false {
520
+ acceptsCharsets(
521
+ charsets?: string | string[],
522
+ ...others: string[]
523
+ ): string[] | string | false {
513
524
  if (!charsets) {
514
525
  return this.accept.charsets();
515
526
  }
516
527
  if (Array.isArray(charsets)) {
517
- charsets = [ ...charsets, ...others ];
528
+ charsets = [...charsets, ...others];
518
529
  } else {
519
- charsets = [ charsets, ...others ];
530
+ charsets = [charsets, ...others];
520
531
  }
521
532
  return this.accept.charsets(...charsets);
522
533
  }
@@ -532,14 +543,17 @@ export class Request {
532
543
  acceptsLanguages(): string[];
533
544
  acceptsLanguages(languages: string[]): string | false;
534
545
  acceptsLanguages(...languages: string[]): string | false;
535
- acceptsLanguages(languages?: string | string[], ...others: string[]): string | string[] | false {
546
+ acceptsLanguages(
547
+ languages?: string | string[],
548
+ ...others: string[]
549
+ ): string | string[] | false {
536
550
  if (!languages) {
537
551
  return this.accept.languages();
538
552
  }
539
553
  if (Array.isArray(languages)) {
540
- languages = [ ...languages, ...others ];
554
+ languages = [...languages, ...others];
541
555
  } else {
542
- languages = [ languages, ...others ];
556
+ languages = [languages, ...others];
543
557
  }
544
558
  return this.accept.languages(...languages);
545
559
  }
@@ -566,9 +580,11 @@ export class Request {
566
580
  * this.is('html'); // => false
567
581
  */
568
582
  is(type?: string | string[], ...types: string[]): string | false | null {
569
- const testTypes: string[] = Array.isArray(type) ? type :
570
- (type ? [ type ] : []);
571
- return typeis(this.req, [ ...testTypes, ...types ]);
583
+ let testTypes: string[] = [];
584
+ if (type) {
585
+ testTypes = Array.isArray(type) ? type : [type];
586
+ }
587
+ return typeis(this.req, [...testTypes, ...types]);
572
588
  }
573
589
 
574
590
  /**
@@ -598,9 +614,9 @@ export class Request {
598
614
  * this.get('Something');
599
615
  * // => ''
600
616
  */
601
- get<T = string | string []>(field: string): T {
617
+ get<T = string | string[]>(field: string): T {
602
618
  const req = this.req;
603
- switch (field = field.toLowerCase()) {
619
+ switch ((field = field.toLowerCase())) {
604
620
  case 'referer':
605
621
  case 'referrer':
606
622
  return (req.headers.referrer || req.headers.referer || '') as T;
@@ -642,8 +658,11 @@ export class Request {
642
658
  *
643
659
  * @param {string} value - The comma-separated value string to split.
644
660
  * @param {number} [limit] - The maximum number of values to return.
645
- * @return {string[]} An array of values from the comma-separated string.
661
+ * @returns {string[]} An array of values from the comma-separated string.
646
662
  */
647
663
  function splitCommaSeparatedValues(value: string, limit?: number): string[] {
648
- return value.split(',', limit).map(v => v.trim()).filter(v => v);
664
+ return value
665
+ .split(',', limit)
666
+ .map(v => v.trim())
667
+ .filter(v => v.length > 0);
649
668
  }
package/src/response.ts CHANGED
@@ -3,7 +3,10 @@ import { extname } from 'node:path';
3
3
  import util from 'node:util';
4
4
  import Stream from 'node:stream';
5
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
6
- import contentDisposition from 'content-disposition';
6
+
7
+ import contentDisposition, {
8
+ type Options as ContentDispositionOptions,
9
+ } from 'content-disposition';
7
10
  import { getType } from 'cache-content-type';
8
11
  import onFinish from 'on-finished';
9
12
  import escape from 'escape-html';
@@ -12,6 +15,7 @@ import statuses from 'statuses';
12
15
  import destroy from 'destroy';
13
16
  import vary from 'vary';
14
17
  import encodeUrl from 'encodeurl';
18
+
15
19
  import type { Application } from './application.js';
16
20
  import type { Context } from './context.js';
17
21
  import type { Request } from './request.js';
@@ -24,7 +28,12 @@ export class Response {
24
28
  ctx: Context;
25
29
  request: Request;
26
30
 
27
- constructor(app: Application, ctx: Context, req: IncomingMessage, res: ServerResponse) {
31
+ constructor(
32
+ app: Application,
33
+ ctx: Context,
34
+ req: IncomingMessage,
35
+ res: ServerResponse
36
+ ) {
28
37
  this.app = app;
29
38
  this.req = req;
30
39
  this.res = res;
@@ -70,17 +79,19 @@ export class Response {
70
79
  assert(code >= 100 && code <= 999, `invalid status code: ${code}`);
71
80
  this._explicitStatus = true;
72
81
  this.res.statusCode = code;
73
- if (this.req.httpVersionMajor < 2) {
74
- this.res.statusMessage = statuses.message[code]!;
82
+ if (this.req.httpVersionMajor < 2 && statuses.message[code]) {
83
+ this.res.statusMessage = statuses.message[code];
84
+ }
85
+ if (this.body && statuses.empty[code]) {
86
+ this.body = null;
75
87
  }
76
- if (this.body && statuses.empty[code]) this.body = null;
77
88
  }
78
89
 
79
90
  /**
80
91
  * Get response status message
81
92
  */
82
93
  get message(): string {
83
- return this.res.statusMessage || statuses.message[this.status]!;
94
+ return this.res.statusMessage ?? statuses.message[this.status];
84
95
  }
85
96
 
86
97
  /**
@@ -90,6 +101,7 @@ export class Response {
90
101
  this.res.statusMessage = msg;
91
102
  }
92
103
 
104
+ // oxlint-disable-next-line typescript/no-explicit-any
93
105
  _body: any;
94
106
  _explicitNullBody: boolean;
95
107
 
@@ -103,14 +115,20 @@ export class Response {
103
115
  /**
104
116
  * Set response body.
105
117
  */
106
- set body(val: string | Buffer | object | Stream | null | undefined | boolean) {
118
+ set body(
119
+ val: string | Buffer | object | Stream | null | undefined | boolean
120
+ ) {
107
121
  const original = this._body;
108
122
  this._body = val;
109
123
 
110
124
  // no content
111
- if (val == null) {
112
- if (!statuses.empty[this.status]) this.status = 204;
113
- if (val === null) this._explicitNullBody = true;
125
+ if (val === null || val === undefined) {
126
+ if (!statuses.empty[this.status]) {
127
+ this.status = 204;
128
+ }
129
+ if (val === null) {
130
+ this._explicitNullBody = true;
131
+ }
114
132
  this.remove('Content-Type');
115
133
  this.remove('Content-Length');
116
134
  this.remove('Transfer-Encoding');
@@ -140,14 +158,18 @@ export class Response {
140
158
  // stream
141
159
  if (val instanceof Stream) {
142
160
  onFinish(this.res, destroy.bind(null, val));
143
- // eslint-disable-next-line eqeqeq
161
+ // oxlint-disable-next-line eqeqeq
144
162
  if (original != val) {
145
163
  val.once('error', err => this.ctx.onerror(err));
146
164
  // overwriting
147
- if (original != null) this.remove('Content-Length');
165
+ if (original !== null && original !== undefined) {
166
+ this.remove('Content-Length');
167
+ }
148
168
  }
149
169
 
150
- if (setType) this.type = 'bin';
170
+ if (setType) {
171
+ this.type = 'bin';
172
+ }
151
173
  return;
152
174
  }
153
175
 
@@ -173,7 +195,7 @@ export class Response {
173
195
  */
174
196
  get length(): number | undefined {
175
197
  if (this.has('Content-Length')) {
176
- return parseInt(this.get('Content-Length'), 10) || 0;
198
+ return Number.parseInt(this.get('Content-Length')) || 0;
177
199
  }
178
200
 
179
201
  const { body } = this;
@@ -246,7 +268,7 @@ export class Response {
246
268
  /**
247
269
  * Set Content-Disposition header to "attachment" with optional `filename`.
248
270
  */
249
- attachment(filename?: string, options?: any) {
271
+ attachment(filename?: string, options?: ContentDispositionOptions) {
250
272
  if (filename) this.type = extname(filename);
251
273
  this.set('Content-Disposition', contentDisposition(filename, options));
252
274
  }
@@ -292,9 +314,11 @@ export class Response {
292
314
  * this.response.is('html', 'json')
293
315
  */
294
316
  is(type?: string | string[], ...types: string[]): string | false {
295
- const testTypes: string[] = Array.isArray(type) ? type :
296
- (type ? [ type ] : []);
297
- return typeis(this.type as string, [ ...testTypes, ...types ]);
317
+ let testTypes: string[] = [];
318
+ if (type) {
319
+ testTypes = Array.isArray(type) ? type : [type];
320
+ }
321
+ return typeis(this.type, [...testTypes, ...types]);
298
322
  }
299
323
 
300
324
  /**
@@ -379,17 +403,21 @@ export class Response {
379
403
  * this.set('Accept', 'application/json');
380
404
  * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
381
405
  */
382
- set(field: string | Record<string, string>, val?: string | number | any[]) {
406
+ set(
407
+ field: string | Record<string, string>,
408
+ val?: string | number | unknown[]
409
+ ) {
383
410
  if (this.headerSent) return;
384
411
  if (typeof field === 'string') {
412
+ let value = val as string | string[];
385
413
  if (Array.isArray(val)) {
386
- val = val.map(v => {
414
+ value = val.map(v => {
387
415
  return typeof v === 'string' ? v : String(v);
388
416
  });
389
417
  } else if (typeof val !== 'string') {
390
- val = String(val);
418
+ value = String(val);
391
419
  }
392
- this.res.setHeader(field, val);
420
+ this.res.setHeader(field, value);
393
421
  } else {
394
422
  for (const key in field) {
395
423
  this.set(key, field[key]);
@@ -408,13 +436,11 @@ export class Response {
408
436
  * this.append('Warning', '199 Miscellaneous warning');
409
437
  */
410
438
  append(field: string, val: string | string[]) {
411
- const prev = this.get(field);
439
+ const prev = this.get<string | string[]>(field);
412
440
 
413
- let value: any | any[] = val;
441
+ let value = val;
414
442
  if (prev) {
415
- value = Array.isArray(prev)
416
- ? prev.concat(value)
417
- : [ prev ].concat(val);
443
+ value = Array.isArray(prev) ? prev.concat(value) : [prev].concat(val);
418
444
  }
419
445
 
420
446
  return this.set(field, value);
package/src/types.ts CHANGED
@@ -4,8 +4,10 @@ export type CustomError = Error & {
4
4
  statusCode?: number;
5
5
  code?: string;
6
6
  expose?: boolean;
7
+ headerSent?: boolean;
7
8
  };
8
9
 
9
- export type AnyProto = {
10
+ export interface AnyProto {
11
+ // oxlint-disable-next-line typescript/no-explicit-any
10
12
  [key: string | symbol]: any;
11
- };
13
+ }