@@ -2,8 +2,6 @@ const fs = require('node:fs');
22const path = require ( 'node:path' ) ;
33const process = require ( 'node:process' ) ;
44const { Buffer } = require ( 'node:buffer' ) ;
5-
6- const co = require ( 'co' ) ;
75const Boom = require ( '@hapi/boom' ) ;
86const camelCase = require ( 'camelcase' ) ;
97const capitalize = require ( 'capitalize' ) ;
@@ -13,15 +11,6 @@ const statuses = require('statuses');
1311const toIdentifier = require ( 'toidentifier' ) ;
1412const { convert } = require ( 'html-to-text' ) ;
1513
16- // lodash
17- const _isError = require ( 'lodash.iserror' ) ;
18- const _isFunction = require ( 'lodash.isfunction' ) ;
19- const _isNumber = require ( 'lodash.isnumber' ) ;
20- const _isObject = require ( 'lodash.isobject' ) ;
21- const _isString = require ( 'lodash.isstring' ) ;
22- const _map = require ( 'lodash.map' ) ;
23- const _values = require ( 'lodash.values' ) ;
24-
2514// <https://github.com/nodejs/node/blob/08dd4b1723b20d56fbedf37d52e736fe09715f80/lib/dns.js#L296-L320>
2615const DNS_RETRY_CODES = new Set ( [
2716 'EADDRGETNETWORKPARAMS' ,
@@ -54,6 +43,33 @@ const opts = {
5443 encoding : 'utf8'
5544} ;
5645
46+ // Helper functions to replace lodash dependencies
47+ function isError ( value ) {
48+ return (
49+ value instanceof Error ||
50+ ( typeof value === 'object' &&
51+ value !== null &&
52+ typeof value . message === 'string' &&
53+ typeof value . name === 'string' )
54+ ) ;
55+ }
56+
57+ function isFunction ( value ) {
58+ return typeof value === 'function' ;
59+ }
60+
61+ function isNumber ( value ) {
62+ return typeof value === 'number' && ! Number . isNaN ( value ) ;
63+ }
64+
65+ function isObject ( value ) {
66+ return value !== null && typeof value === 'object' ;
67+ }
68+
69+ function isString ( value ) {
70+ return typeof value === 'string' ;
71+ }
72+
5773function isErrorConstructorName ( err , name ) {
5874 const names = [ ] ;
5975
@@ -130,18 +146,34 @@ function errorHandler(
130146 try {
131147 if ( ! err ) return ;
132148
133- this . app . emit ( 'error' , err , this ) ;
149+ if ( ! isError ( err ) ) err = new Error ( err ) ;
150+
151+ // check if we have a boom error that specified
152+ // a status code already for us (and then use it)
153+ if ( isObject ( err . output ) && isNumber ( err . output . statusCode ) ) {
154+ err . status = err . output . statusCode ;
155+ } else if ( isString ( err . code ) && DNS_RETRY_CODES . has ( err . code ) ) {
156+ // check if this was a DNS error and if so
157+ // then set status code for retries appropriately
158+ err . status = 408 ;
159+ }
160+
161+ if ( ! isNumber ( err . status ) ) err . status = 500 ;
162+
163+ // set err.statusCode for consistency
164+ err . statusCode = err . status ;
134165
135166 // nothing we can do here other
136167 // than delegate to the app-level
137168 // handler and log.
138- if ( this . headerSent || ! this . writable ) return ;
169+ if ( this . headerSent || ! this . writable ) {
170+ this . app . emit ( 'error' , err , this ) ;
171+ return ;
172+ }
139173
140174 // translate messages
141175 const translate = ( message ) =>
142- _isFunction ( this . request . t ) ? this . request . t ( message ) : message ;
143-
144- if ( ! _isError ( err ) ) err = new Error ( err ) ;
176+ isFunction ( this . request . t ) ? this . request . t ( message ) : message ;
145177
146178 const type = this . accepts ( [ 'text' , 'json' , 'html' ] ) ;
147179
@@ -151,7 +183,7 @@ function errorHandler(
151183 }
152184
153185 const val = Number . parseInt ( err . message , 10 ) ;
154- if ( _isNumber ( val ) && val >= 400 && val < 600 ) {
186+ if ( isNumber ( val ) && val >= 400 && val < 600 ) {
155187 // check if we threw just a status code in order to keep it simple
156188 err = Boom [ camelCase ( toIdentifier ( statuses . message [ val ] ) ) ] ( ) ;
157189 err . message = translate ( err . message ) ;
@@ -209,32 +241,22 @@ function errorHandler(
209241 err . message = translate ( Boom . internal ( ) . output . payload . message ) ;
210242 }
211243
212- // check if we have a boom error that specified
213- // a status code already for us (and then use it)
214- if ( _isObject ( err . output ) && _isNumber ( err . output . statusCode ) ) {
215- err . status = err . output . statusCode ;
216- } else if ( _isString ( err . code ) && DNS_RETRY_CODES . has ( err . code ) ) {
217- // check if this was a DNS error and if so
218- // then set status code for retries appropriately
219- err . status = 408 ;
220- err . message = translate ( Boom . clientTimeout ( ) . output . payload . message ) ;
221- }
244+ // finalize status code after all error type checks
245+ err . status ||= 500 ;
246+ err . statusCode = err . status ;
222247
223- if ( ! _isNumber ( err . status ) ) err . status = 500 ;
248+ // emit error event AFTER status code has been determined
249+ // so that error listeners (e.g. loggers) have access to the correct status
250+ this . app . emit ( 'error' , err , this ) ;
224251
225252 // check if there is flash messaging
226- const hasFlash = _isFunction ( this . flash ) ;
253+ const hasFlash = isFunction ( this . flash ) ;
227254
228255 // check if there is a view rendering engine binding `this.render`
229- const hasRender = _isFunction ( this . render ) ;
256+ const hasRender = isFunction ( this . render ) ;
230257
231258 // check if we're about to go into a possible endless redirect loop
232259 const noReferrer = this . get ( 'Referrer' ) === '' ;
233-
234- // populate the status and body with `boom` error message payload
235- // (e.g. you can do `ctx.throw(404)` and it will output a beautiful err obj)
236- err . status = err . status || 500 ;
237- err . statusCode = err . status ;
238260 this . statusCode = err . statusCode ;
239261 this . status = this . statusCode ;
240262
@@ -246,7 +268,7 @@ function errorHandler(
246268
247269 // set any additional error headers specified
248270 // (e.g. for BasicAuth we use `basic-auth` which specifies WWW-Authenticate)
249- if ( _isObject ( err . headers ) && Object . keys ( err . headers ) . length > 0 )
271+ if ( isObject ( err . headers ) && Object . keys ( err . headers ) . length > 0 )
250272 this . set ( err . headers ) ;
251273
252274 // fix page title and description
@@ -304,9 +326,12 @@ function errorHandler(
304326 cookiesKey
305327 ) {
306328 try {
307- await co
308- . wrap ( this . sessionStore . set )
309- . call ( this . sessionStore , this . sessionId , this . session ) ;
329+ await new Promise ( ( resolve , reject ) => {
330+ this . sessionStore . set ( this . sessionId , this . session , ( err ) => {
331+ if ( err ) reject ( err ) ;
332+ else resolve ( ) ;
333+ } ) ;
334+ } ) ;
310335 this . cookies . set (
311336 cookiesKey ,
312337 this . sessionId ,
@@ -326,11 +351,16 @@ function errorHandler(
326351 //
327352 // if we're using `koa-session-store` we need to add
328353 // `this._session = new Session()`, and then run this:
329- await co.wrap(this._session._store.save).call(
330- this._session._store,
331- this._session._sid,
332- stringify(this.session)
333- );
354+ await new Promise((resolve, reject) => {
355+ this._session._store.save(
356+ this._session._sid,
357+ stringify(this.session),
358+ (err) => {
359+ if (err) reject(err);
360+ else resolve();
361+ }
362+ );
363+ });
334364 this.cookies.set(this._session._name, stringify({
335365 _sid: this._session._sid
336366 }), this._session._cookieOpts);
@@ -391,28 +421,31 @@ function makeAPIFriendly(ctx, message) {
391421function parseValidationError ( ctx , err , translate ) {
392422 // transform the error messages to be humanized as adapted from:
393423 // https://github.com/niftylettuce/mongoose-validation-error-transform
394- err . errors = _map ( err . errors , ( error ) => {
395- if ( ! _isString ( error . path ) ) {
396- error . message = capitalize ( error . message ) ;
397- return error ;
398- }
424+ err . errors = Object . fromEntries (
425+ Object . entries ( err . errors ) . map ( ( [ key , error ] ) => {
426+ if ( ! isString ( error . path ) ) {
427+ error . message = capitalize ( error . message ) ;
428+ return [ key , error ] ;
429+ }
399430
400- error . message = error . message . replace (
401- new RegExp ( error . path , 'g' ) ,
402- humanize ( error . path )
403- ) ;
404- error . message = capitalize ( error . message ) ;
405- return error ;
406- } ) ;
431+ error . message = error . message . replaceAll (
432+ new RegExp ( error . path , 'g' ) ,
433+ humanize ( error . path )
434+ ) ;
435+ error . message = capitalize ( error . message ) ;
436+ return [ key , error ] ;
437+ } )
438+ ) ;
407439
408440 // loop over the errors object of the Validation Error
409441 // with support for HTML error lists
410- if ( _values ( err . errors ) . length === 1 ) {
411- err . message = _values ( err . errors ) [ 0 ] . message ;
442+ const errorValues = Object . values ( err . errors ) ;
443+ if ( errorValues . length === 1 ) {
444+ err . message = errorValues [ 0 ] . message ;
412445 if ( ! err . no_translate ) err . message = translate ( err . message ) ;
413446 } else {
414- const errors = _map ( _map ( _values ( err . errors ) , 'message' ) , ( message ) =>
415- err . no_translate ? message : translate ( message )
447+ const errors = errorValues . map ( ( error ) =>
448+ err . no_translate ? error . message : translate ( error . message )
416449 ) ;
417450 err . message = makeAPIFriendly (
418451 ctx ,
0 commit comments