1
- import { performance } from 'node:perf_hooks' ;
2
1
import delegate from 'delegates' ;
3
2
import { assign } from 'utility' ;
4
- import { utils } from '@eggjs/core' ;
3
+ import { now , diff } from 'performance-ms' ;
4
+ import {
5
+ utils , Context as EggCoreContext , Router ,
6
+ type ContextDelegation as EggCoreContextDelegation ,
7
+ } from '@eggjs/core' ;
8
+ import type { Cookies as ContextCookies } from '@eggjs/cookies' ;
9
+ import type { Application } from '../../lib/application.js' ;
10
+ import type { ContextHttpClient } from '../../lib/core/context_httpclient.js' ;
11
+ import type { BaseContextClass } from '../../lib//core/base_context_class.js' ;
12
+ import Request from './request.js' ;
13
+ import Response from './response.js' ;
14
+ import { EggLogger } from 'egg-logger' ;
5
15
6
- const HELPER = Symbol ( 'Context#helper' ) ;
7
- const LOCALS = Symbol ( 'Context#locals' ) ;
8
- const LOCALS_LIST = Symbol ( 'Context#localsList' ) ;
9
- const COOKIES = Symbol ( 'Context#cookies' ) ;
10
- const CONTEXT_LOGGERS = Symbol ( 'Context#logger' ) ;
11
- const CONTEXT_HTTPCLIENT = Symbol ( 'Context#httpclient' ) ;
12
- const CONTEXT_ROUTER = Symbol ( 'Context#router' ) ;
16
+ const HELPER = Symbol ( 'ctx helper' ) ;
17
+ const LOCALS = Symbol ( 'ctx locals' ) ;
18
+ const LOCALS_LIST = Symbol ( 'ctx localsList' ) ;
19
+ const COOKIES = Symbol ( 'ctx cookies' ) ;
20
+ const CONTEXT_HTTPCLIENT = Symbol ( 'ctx httpclient' ) ;
21
+ const CONTEXT_ROUTER = Symbol ( 'ctx router' ) ;
22
+
23
+ interface Cookies extends ContextCookies {
24
+ request : any ;
25
+ response : any ;
26
+ }
27
+
28
+ export default class Context extends EggCoreContext {
29
+ declare app : Application ;
30
+ declare request : Request ;
31
+ declare service : BaseContextClass ;
32
+
33
+ /**
34
+ * Request start time
35
+ * @member {Number} Context#starttime
36
+ */
37
+ starttime : number ;
38
+ /**
39
+ * Request start timer using `performance.now()`
40
+ * @member {Number} Context#performanceStarttime
41
+ */
42
+ performanceStarttime : number ;
13
43
14
- const Context = {
15
44
/**
16
45
* Get the current visitor's cookies.
17
46
*/
18
47
get cookies ( ) {
19
- if ( ! this [ COOKIES ] ) {
20
- this [ COOKIES ] = new this . app . ContextCookies ( this , this . app . keys , this . app . config . cookies ) ;
48
+ let cookies = this [ COOKIES ] ;
49
+ if ( ! cookies ) {
50
+ this [ COOKIES ] = cookies = new this . app . ContextCookies ( this , this . app . keys , this . app . config . cookies ) ;
21
51
}
22
- return this [ COOKIES ] ;
23
- } ,
52
+ return cookies as Cookies ;
53
+ }
24
54
25
55
/**
26
56
* Get a wrapper httpclient instance contain ctx in the hold request process
27
57
*
28
58
* @return {ContextHttpClient } the wrapper httpclient instance
29
59
*/
30
- get httpclient ( ) {
60
+ get httpclient ( ) : ContextHttpClient {
31
61
if ( ! this [ CONTEXT_HTTPCLIENT ] ) {
32
- this [ CONTEXT_HTTPCLIENT ] = new this . app . ContextHttpClient ( this ) ;
62
+ this [ CONTEXT_HTTPCLIENT ] = new this . app . ContextHttpClient ( this as any ) ;
33
63
}
34
- return this [ CONTEXT_HTTPCLIENT ] ;
35
- } ,
64
+ return this [ CONTEXT_HTTPCLIENT ] as ContextHttpClient ;
65
+ }
66
+
67
+ /**
68
+ * Alias to {@link Context#httpclient}
69
+ */
70
+ get httpClient ( ) : ContextHttpClient {
71
+ return this . httpclient ;
72
+ }
36
73
37
74
/**
38
75
* Shortcut for httpclient.curl
@@ -42,9 +79,9 @@ const Context = {
42
79
* @param {Object } [options] - options for request.
43
80
* @return {Object } see {@link ContextHttpClient#curl}
44
81
*/
45
- curl ( url : string , options ?: object ) {
46
- return this . httpclient . curl ( url , options ) ;
47
- } ,
82
+ async curl ( url : string , options ?: object ) : ReturnType < ContextHttpClient [ 'curl' ] > {
83
+ return await this . httpclient . curl ( url , options ) ;
84
+ }
48
85
49
86
/**
50
87
* Alias to {@link Application#router}
@@ -56,20 +93,17 @@ const Context = {
56
93
* this.router.pathFor('post', { id: 12 });
57
94
* ```
58
95
*/
59
- get router ( ) {
60
- if ( ! this [ CONTEXT_ROUTER ] ) {
61
- this [ CONTEXT_ROUTER ] = this . app . router ;
62
- }
63
- return this [ CONTEXT_ROUTER ] ;
64
- } ,
96
+ get router ( ) : Router {
97
+ return this . app . router ;
98
+ }
65
99
66
100
/**
67
101
* Set router to Context, only use on EggRouter
68
- * @param {EggRouter } val router instance
102
+ * @param {Router } val router instance
69
103
*/
70
- set router ( val ) {
104
+ set router ( val : Router ) {
71
105
this [ CONTEXT_ROUTER ] = val ;
72
- } ,
106
+ }
73
107
74
108
/**
75
109
* Get helper instance from {@link Application#Helper}
@@ -79,64 +113,45 @@ const Context = {
79
113
*/
80
114
get helper ( ) {
81
115
if ( ! this [ HELPER ] ) {
82
- this [ HELPER ] = new this . app . Helper ( this ) ;
116
+ this [ HELPER ] = new this . app . Helper ( this as any ) ;
83
117
}
84
118
return this [ HELPER ] ;
85
- } ,
119
+ }
86
120
87
121
/**
88
122
* Wrap app.loggers with context information,
89
123
* if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`
90
124
*
91
125
* @param {String } name - logger name
92
- * @return {Logger } logger
93
126
*/
94
- getLogger ( name : string ) {
95
- if ( this . app . config . logger . enableFastContextLogger ) {
96
- return this . app . getLogger ( name ) ;
97
- }
98
- let cache = this [ CONTEXT_LOGGERS ] ;
99
- if ( ! cache ) {
100
- cache = this [ CONTEXT_LOGGERS ] = { } ;
101
- }
102
-
103
- // read from cache
104
- if ( cache [ name ] ) return cache [ name ] ;
105
-
106
- // get no exist logger
107
- const appLogger = this . app . getLogger ( name ) ;
108
- if ( ! appLogger ) return null ;
109
-
110
- // write to cache
111
- cache [ name ] = new this . app . ContextLogger ( this , appLogger ) ;
112
- return cache [ name ] ;
113
- } ,
127
+ getLogger ( name : string ) : EggLogger {
128
+ return this . app . getLogger ( name ) ;
129
+ }
114
130
115
131
/**
116
- * Logger for Application, wrapping app.coreLogger with context infomation
132
+ * Logger for Application
117
133
*
118
- * @member {ContextLogger } Context#logger
134
+ * @member {Logger } Context#logger
119
135
* @since 1.0.0
120
136
* @example
121
137
* ```js
122
138
* this.logger.info('some request data: %j', this.request.body);
123
139
* this.logger.warn('WARNING!!!!');
124
140
* ```
125
141
*/
126
- get logger ( ) {
142
+ get logger ( ) : EggLogger {
127
143
return this . getLogger ( 'logger' ) ;
128
- } ,
144
+ }
129
145
130
146
/**
131
- * Logger for frameworks and plugins,
132
- * wrapping app.coreLogger with context infomation
147
+ * Logger for frameworks and plugins
133
148
*
134
- * @member {ContextLogger } Context#coreLogger
149
+ * @member {Logger } Context#coreLogger
135
150
* @since 1.0.0
136
151
*/
137
- get coreLogger ( ) {
152
+ get coreLogger ( ) : EggLogger {
138
153
return this . getLogger ( 'coreLogger' ) ;
139
- } ,
154
+ }
140
155
141
156
/**
142
157
* locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,
@@ -169,19 +184,18 @@ const Context = {
169
184
if ( ! this [ LOCALS ] ) {
170
185
this [ LOCALS ] = assign ( { } , this . app . locals ) ;
171
186
}
172
- if ( this [ LOCALS_LIST ] && this [ LOCALS_LIST ] . length ) {
187
+ if ( Array . isArray ( this [ LOCALS_LIST ] ) && this [ LOCALS_LIST ] . length > 0 ) {
173
188
assign ( this [ LOCALS ] , this [ LOCALS_LIST ] ) ;
174
189
this [ LOCALS_LIST ] = null ;
175
190
}
176
- return this [ LOCALS ] ;
177
- } ,
191
+ return this [ LOCALS ] as Record < string , any > ;
192
+ }
178
193
179
194
set locals ( val ) {
180
- if ( ! this [ LOCALS_LIST ] ) {
181
- this [ LOCALS_LIST ] = [ ] ;
182
- }
183
- this [ LOCALS_LIST ] . push ( val ) ;
184
- } ,
195
+ const localsList = this [ LOCALS_LIST ] as Record < string , any > [ ] ?? [ ] ;
196
+ localsList . push ( val ) ;
197
+ this [ LOCALS_LIST ] = localsList ;
198
+ }
185
199
186
200
/**
187
201
* alias to {@link Context#locals}, compatible with koa that use this variable
@@ -190,11 +204,11 @@ const Context = {
190
204
*/
191
205
get state ( ) {
192
206
return this . locals ;
193
- } ,
207
+ }
194
208
195
209
set state ( val ) {
196
210
this . locals = val ;
197
- } ,
211
+ }
198
212
199
213
/**
200
214
* Run async function in the background
@@ -208,43 +222,40 @@ const Context = {
208
222
* });
209
223
* ```
210
224
*/
211
- runInBackground ( scope : ( ctx : any ) => Promise < void > ) {
225
+ runInBackground ( scope : ( ctx : ContextDelegation ) => Promise < void > , taskName ?: string ) : void {
212
226
// try to use custom function name first
213
- /* istanbul ignore next */
214
- const taskName = Reflect . get ( scope , '_name' ) || scope . name || utils . getCalleeFromStack ( true ) ;
215
- this . _runInBackground ( scope , taskName ) ;
216
- } ,
227
+ if ( ! taskName ) {
228
+ taskName = Reflect . get ( scope , '_name' ) || scope . name || utils . getCalleeFromStack ( true ) ;
229
+ }
230
+ // use setImmediate to ensure all sync logic will run async
231
+ setImmediate ( ( ) => {
232
+ this . _runInBackground ( scope , taskName ! ) ;
233
+ } ) ;
234
+ }
217
235
218
236
// let plugins or frameworks to reuse _runInBackground in some cases.
219
237
// e.g.: https://github.com/eggjs/egg-mock/pull/78
220
- _runInBackground ( scope : ( ctx : any ) => Promise < void > , taskName : string ) {
221
- // eslint-disable-next-line @typescript-eslint/no-this-alias
222
- const ctx = this ;
223
- const start = performance . now ( ) ;
224
- // use setImmediate to ensure all sync logic will run async
225
- return new Promise ( resolve => setImmediate ( resolve ) )
226
- . then ( ( ) => scope ( ctx ) )
227
- . then ( ( ) => {
228
- ctx . coreLogger . info ( '[egg:background] task:%s success (%dms)' ,
229
- taskName , Math . floor ( ( performance . now ( ) - start ) * 1000 ) / 1000 ) ;
230
- } )
231
- . catch ( err => {
232
- // background task process log
233
- ctx . coreLogger . info ( '[egg:background] task:%s fail (%dms)' ,
234
- taskName , Math . floor ( ( performance . now ( ) - start ) * 1000 ) / 1000 ) ;
238
+ async _runInBackground ( scope : ( ctx : ContextDelegation ) => Promise < void > , taskName : string ) {
239
+ const startTime = now ( ) ;
240
+ try {
241
+ await scope ( this as any ) ;
242
+ this . coreLogger . info ( '[egg:background] task:%s success (%dms)' , taskName , diff ( startTime ) ) ;
243
+ } catch ( err : any ) {
244
+ // background task process log
245
+ this . coreLogger . info ( '[egg:background] task:%s fail (%dms)' , taskName , diff ( startTime ) ) ;
235
246
236
- // emit error when promise catch, and set err.runInBackground flag
237
- err . runInBackground = true ;
238
- ctx . app . emit ( 'error' , err , ctx ) ;
239
- } ) ;
240
- } ,
241
- } as any ;
247
+ // emit error when promise catch, and set err.runInBackground flag
248
+ err . runInBackground = true ;
249
+ this . app . emit ( 'error' , err , this ) ;
250
+ }
251
+ }
252
+ }
242
253
243
254
/**
244
255
* Context delegation.
245
256
*/
246
257
247
- delegate ( Context , 'request' )
258
+ delegate ( Context . prototype , 'request' )
248
259
/**
249
260
* @member {Boolean} Context#acceptJSON
250
261
* @see Request#acceptJSON
@@ -270,12 +281,14 @@ delegate(Context, 'request')
270
281
*/
271
282
. access ( 'ip' ) ;
272
283
273
- delegate ( Context , 'response' )
284
+ delegate ( Context . prototype , 'response' )
274
285
/**
275
286
* @member {Number} Context#realStatus
276
287
* @see Response#realStatus
277
288
* @since 1.0.0
278
289
*/
279
290
. access ( 'realStatus' ) ;
280
291
281
- export default Context ;
292
+ export type ContextDelegation = EggCoreContextDelegation & Context
293
+ & Pick < Request , 'acceptJSON' | 'queries' | 'accept' | 'ip' >
294
+ & Pick < Response , 'realStatus' > ;
0 commit comments