@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.
- package/Readme.md +8 -8
- package/dist/commonjs/application.d.ts +12 -12
- package/dist/commonjs/application.js +60 -22
- package/dist/commonjs/context.d.ts +9 -11
- package/dist/commonjs/context.js +31 -15
- package/dist/commonjs/index.d.ts +1 -1
- package/dist/commonjs/index.js +1 -2
- package/dist/commonjs/request.d.ts +3 -2
- package/dist/commonjs/request.js +35 -64
- package/dist/commonjs/response.d.ts +3 -2
- package/dist/commonjs/response.js +26 -19
- package/dist/commonjs/types.d.ts +3 -2
- package/dist/esm/application.d.ts +12 -12
- package/dist/esm/application.js +25 -20
- package/dist/esm/context.d.ts +9 -11
- package/dist/esm/context.js +31 -15
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/request.d.ts +3 -2
- package/dist/esm/request.js +35 -64
- package/dist/esm/response.d.ts +3 -2
- package/dist/esm/response.js +26 -19
- package/dist/esm/types.d.ts +3 -2
- package/dist/package.json +1 -1
- package/package.json +18 -10
- package/src/application.ts +48 -30
- package/src/context.ts +85 -34
- package/src/index.ts +1 -1
- package/src/request.ts +73 -54
- package/src/response.ts +53 -27
- package/src/types.ts +4 -2
package/src/application.ts
CHANGED
|
@@ -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
|
-
|
|
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> & {
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 =
|
|
91
|
+
this.ContextClass =
|
|
92
|
+
class ApplicationContext extends Context {} as ProtoImplClass<Context>;
|
|
88
93
|
this.context = this.ContextClass.prototype;
|
|
89
|
-
this.RequestClass =
|
|
94
|
+
this.RequestClass =
|
|
95
|
+
class ApplicationRequest extends Request {} as ProtoImplClass<Request>;
|
|
90
96
|
this.request = this.RequestClass.prototype;
|
|
91
|
-
this.ResponseClass =
|
|
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')
|
|
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(
|
|
159
|
-
|
|
160
|
-
|
|
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(
|
|
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:
|
|
204
|
-
|
|
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 =
|
|
250
|
+
const isNativeError =
|
|
251
|
+
err instanceof Error ||
|
|
236
252
|
Object.prototype.toString.call(err) === '[object Error]';
|
|
237
|
-
if (!isNativeError)
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
209
|
+
for (const name of res.getHeaderNames()) {
|
|
210
|
+
res.removeHeader(name);
|
|
211
|
+
}
|
|
184
212
|
|
|
185
213
|
// then set those specified
|
|
186
|
-
if (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')
|
|
224
|
+
if (err.code === 'ENOENT') {
|
|
225
|
+
statusCode = 404;
|
|
226
|
+
}
|
|
195
227
|
|
|
196
228
|
// default to 500
|
|
197
|
-
if (typeof statusCode !== 'number' || !statuses.message[statusCode])
|
|
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
|
-
|
|
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(
|
|
235
|
-
|
|
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(
|
|
242
|
-
|
|
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(
|
|
249
|
-
|
|
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(
|
|
253
|
-
|
|
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
|
|
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