@nxtedition/lib 23.3.18 → 23.3.20
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/app.js +3 -1
- package/http.js +143 -134
- package/package.json +1 -1
- package/rxjs/combineMap.d.ts +6 -0
- package/serializers.js +15 -5
- package/trace.js +14 -16
package/app.js
CHANGED
|
@@ -272,7 +272,9 @@ export function makeApp(appConfig, onTerminate) {
|
|
|
272
272
|
try {
|
|
273
273
|
fs.mkdirSync('./.nxt', { recursive: true })
|
|
274
274
|
} catch (err) {
|
|
275
|
-
|
|
275
|
+
if (err.code !== 'EACCES') {
|
|
276
|
+
logger.warn({ err }, 'failed to create ./nxt dir')
|
|
277
|
+
}
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
let toobusy
|
package/http.js
CHANGED
|
@@ -65,12 +65,10 @@ export class Context {
|
|
|
65
65
|
return this.#userAgent
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/** @deprecated */
|
|
69
68
|
get method() {
|
|
70
69
|
return this.#req.method
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
/** @deprecated */
|
|
74
72
|
get url() {
|
|
75
73
|
if (this.#url === undefined) {
|
|
76
74
|
this.#url = requestTarget(this.#req)
|
|
@@ -78,8 +76,11 @@ export class Context {
|
|
|
78
76
|
return this.#url
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
/** @deprecated */
|
|
82
79
|
set url(value) {
|
|
80
|
+
if (value instanceof URL) {
|
|
81
|
+
value = value.toString()
|
|
82
|
+
}
|
|
83
|
+
|
|
83
84
|
if (typeof value !== 'string') {
|
|
84
85
|
throw new Error('url must be a string')
|
|
85
86
|
}
|
|
@@ -89,14 +90,16 @@ export class Context {
|
|
|
89
90
|
this.#query = undefined
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
/** @deprecated */
|
|
93
93
|
get query() {
|
|
94
94
|
if (this.#query === undefined) {
|
|
95
|
-
this.#query =
|
|
95
|
+
this.#query =
|
|
96
|
+
this.url.search.length > 1 ? Object.freeze(querystring.parse(this.url.search.slice(1))) : {}
|
|
96
97
|
}
|
|
97
98
|
return this.#query
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
set query(value) {}
|
|
102
|
+
|
|
100
103
|
get logger() {
|
|
101
104
|
return this.#logger
|
|
102
105
|
}
|
|
@@ -251,135 +254,6 @@ export async function requestMiddleware(ctx, next) {
|
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
|
|
254
|
-
export async function request(ctx, next) {
|
|
255
|
-
const { req, res, logger } = ctx
|
|
256
|
-
const startTime = performance.now()
|
|
257
|
-
|
|
258
|
-
const ac = ctx.signal !== undefined ? null : new AbortController()
|
|
259
|
-
|
|
260
|
-
let reqLogger = logger
|
|
261
|
-
|
|
262
|
-
pendingSet.add(ctx)
|
|
263
|
-
try {
|
|
264
|
-
ctx.url = requestTarget(req)
|
|
265
|
-
if (!ctx.url) {
|
|
266
|
-
throw new createError.BadRequest('invalid url')
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
ctx.id = req.id = res.id = req.headers['request-id'] || genReqId()
|
|
270
|
-
ctx.userAgent = req['user-agent'] || req['User-Agent'] || ''
|
|
271
|
-
ctx.method = req.method
|
|
272
|
-
ctx.query = ctx.url.search.length > 1 ? querystring.parse(ctx.url.search.slice(1)) : {}
|
|
273
|
-
ctx.logger =
|
|
274
|
-
req.log =
|
|
275
|
-
res.log =
|
|
276
|
-
logger.child({ req: { id: req.id, url: req.url, userAgent: ctx.userAgent } })
|
|
277
|
-
|
|
278
|
-
if (ac) {
|
|
279
|
-
ctx.signal = ac.signal
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
283
|
-
req.resume() // Dump the body if there is one.
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
res.setHeader('request-id', req.id)
|
|
287
|
-
|
|
288
|
-
const isHealthcheck = ctx.url.pathname === '/healthcheck' || ctx.url.pathname === '/_up'
|
|
289
|
-
|
|
290
|
-
reqLogger = logger.child({ req })
|
|
291
|
-
if (!isHealthcheck) {
|
|
292
|
-
reqLogger.debug('request started')
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const nextPromise = next()
|
|
296
|
-
const responsePromise = new Promise((resolve, reject) => {
|
|
297
|
-
res.on('timeout', onTimeout).on('error', reject).on('close', resolve)
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
if (nextPromise) {
|
|
301
|
-
await Promise.all([nextPromise, responsePromise])
|
|
302
|
-
} else if (!res.closed) {
|
|
303
|
-
await responsePromise
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const elapsedTime = performance.now() - startTime
|
|
307
|
-
|
|
308
|
-
if (isHealthcheck) {
|
|
309
|
-
// Do nothing...
|
|
310
|
-
} else if (!res.writableEnded) {
|
|
311
|
-
reqLogger.debug({ res, elapsedTime }, 'request aborted')
|
|
312
|
-
} else if (res.statusCode >= 500) {
|
|
313
|
-
reqLogger.error({ res, elapsedTime }, 'request error')
|
|
314
|
-
} else if (res.statusCode >= 400) {
|
|
315
|
-
reqLogger.warn({ res, elapsedTime }, 'request failed')
|
|
316
|
-
} else {
|
|
317
|
-
reqLogger.debug({ res, elapsedTime }, 'request completed')
|
|
318
|
-
}
|
|
319
|
-
} catch (err) {
|
|
320
|
-
ac?.abort(err)
|
|
321
|
-
|
|
322
|
-
const statusCode = err.statusCode || err.$metadata?.httpStatusCode || 500
|
|
323
|
-
const elapsedTime = performance.now() - startTime
|
|
324
|
-
|
|
325
|
-
if (!res.headersSent && !res.destroyed) {
|
|
326
|
-
res.statusCode = statusCode
|
|
327
|
-
|
|
328
|
-
let reqId = req?.id || err.id
|
|
329
|
-
for (const name of res.getHeaderNames()) {
|
|
330
|
-
if (!reqId && name === 'request-id') {
|
|
331
|
-
reqId = res.getHeader(name)
|
|
332
|
-
}
|
|
333
|
-
res.removeHeader(name)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (reqId) {
|
|
337
|
-
res.setHeader('request-id', reqId)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (fp.isPlainObject(err.headers)) {
|
|
341
|
-
for (const [key, val] of Object.entries(err.headers)) {
|
|
342
|
-
if (!ERR_HEADER_EXPR.test(key)) {
|
|
343
|
-
res.setHeader(key, val)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (fp.isPlainObject(err.body)) {
|
|
349
|
-
res.setHeader('content-type', 'application/json')
|
|
350
|
-
res.end(JSON.stringify(err.body))
|
|
351
|
-
} else if (typeof err.body === 'string') {
|
|
352
|
-
res.end(err.body)
|
|
353
|
-
} else if (Buffer.isBuffer(err.body)) {
|
|
354
|
-
res.end(err.body)
|
|
355
|
-
} else {
|
|
356
|
-
res.end()
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (statusCode < 500) {
|
|
360
|
-
reqLogger.warn({ res, err, elapsedTime }, 'request failed')
|
|
361
|
-
} else {
|
|
362
|
-
reqLogger.error({ res, err, elapsedTime }, 'request error')
|
|
363
|
-
}
|
|
364
|
-
} else {
|
|
365
|
-
if (req.aborted || !res.writableEnded || err.name === 'AbortError') {
|
|
366
|
-
reqLogger.debug({ res, err, elapsedTime }, 'request aborted')
|
|
367
|
-
} else if (statusCode < 500) {
|
|
368
|
-
reqLogger.warn({ res, err, elapsedTime }, 'request failed')
|
|
369
|
-
} else {
|
|
370
|
-
reqLogger.error({ res, err, elapsedTime }, 'request error')
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
} finally {
|
|
374
|
-
pendingSet.delete(ctx)
|
|
375
|
-
|
|
376
|
-
if (!res.writableEnded) {
|
|
377
|
-
res.destroy()
|
|
378
|
-
logger.debug('request destroyed')
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
257
|
export class ServerResponse extends http.ServerResponse {
|
|
384
258
|
#created = 0
|
|
385
259
|
#bytesWritten = 0
|
|
@@ -570,6 +444,137 @@ export class Http2ServerResponse extends http2.Http2ServerResponse {
|
|
|
570
444
|
}
|
|
571
445
|
}
|
|
572
446
|
|
|
447
|
+
/** @deprecated */
|
|
448
|
+
export async function request(ctx, next) {
|
|
449
|
+
const { req, res, logger } = ctx
|
|
450
|
+
const startTime = performance.now()
|
|
451
|
+
|
|
452
|
+
const ac = ctx.signal !== undefined ? null : new AbortController()
|
|
453
|
+
|
|
454
|
+
let reqLogger = logger
|
|
455
|
+
|
|
456
|
+
pendingSet.add(ctx)
|
|
457
|
+
try {
|
|
458
|
+
ctx.url = requestTarget(req)
|
|
459
|
+
if (!ctx.url) {
|
|
460
|
+
throw new createError.BadRequest('invalid url')
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
ctx.id = req.id = res.id = req.headers['request-id'] || genReqId()
|
|
464
|
+
ctx.userAgent = req['user-agent'] || req['User-Agent'] || ''
|
|
465
|
+
ctx.method = req.method
|
|
466
|
+
ctx.query = ctx.url.search.length > 1 ? querystring.parse(ctx.url.search.slice(1)) : {}
|
|
467
|
+
ctx.logger =
|
|
468
|
+
req.log =
|
|
469
|
+
res.log =
|
|
470
|
+
logger.child({ req: { id: req.id, url: req.url, userAgent: ctx.userAgent } })
|
|
471
|
+
|
|
472
|
+
if (ac) {
|
|
473
|
+
ctx.signal = ac.signal
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
477
|
+
req.resume() // Dump the body if there is one.
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
res.setHeader('request-id', req.id)
|
|
481
|
+
|
|
482
|
+
const isHealthcheck = ctx.url.pathname === '/healthcheck' || ctx.url.pathname === '/_up'
|
|
483
|
+
|
|
484
|
+
reqLogger = logger.child({ req })
|
|
485
|
+
if (!isHealthcheck) {
|
|
486
|
+
reqLogger.debug('request started')
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const nextPromise = next()
|
|
490
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
491
|
+
res.on('timeout', onTimeout).on('error', reject).on('close', resolve)
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
if (nextPromise) {
|
|
495
|
+
await Promise.all([nextPromise, responsePromise])
|
|
496
|
+
} else if (!res.closed) {
|
|
497
|
+
await responsePromise
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const elapsedTime = performance.now() - startTime
|
|
501
|
+
|
|
502
|
+
if (isHealthcheck) {
|
|
503
|
+
// Do nothing...
|
|
504
|
+
} else if (!res.writableEnded) {
|
|
505
|
+
reqLogger.debug({ res, elapsedTime }, 'request aborted')
|
|
506
|
+
} else if (res.statusCode >= 500) {
|
|
507
|
+
reqLogger.error({ res, elapsedTime }, 'request error')
|
|
508
|
+
} else if (res.statusCode >= 400) {
|
|
509
|
+
reqLogger.warn({ res, elapsedTime }, 'request failed')
|
|
510
|
+
} else {
|
|
511
|
+
reqLogger.debug({ res, elapsedTime }, 'request completed')
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
ac?.abort(err)
|
|
515
|
+
|
|
516
|
+
const statusCode = err.statusCode || err.$metadata?.httpStatusCode || 500
|
|
517
|
+
const elapsedTime = performance.now() - startTime
|
|
518
|
+
|
|
519
|
+
if (!res.headersSent && !res.destroyed) {
|
|
520
|
+
res.statusCode = statusCode
|
|
521
|
+
|
|
522
|
+
let reqId = req?.id || err.id
|
|
523
|
+
for (const name of res.getHeaderNames()) {
|
|
524
|
+
if (!reqId && name === 'request-id') {
|
|
525
|
+
reqId = res.getHeader(name)
|
|
526
|
+
}
|
|
527
|
+
res.removeHeader(name)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (reqId) {
|
|
531
|
+
res.setHeader('request-id', reqId)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (fp.isPlainObject(err.headers)) {
|
|
535
|
+
for (const [key, val] of Object.entries(err.headers)) {
|
|
536
|
+
if (!ERR_HEADER_EXPR.test(key)) {
|
|
537
|
+
res.setHeader(key, val)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (fp.isPlainObject(err.body)) {
|
|
543
|
+
res.setHeader('content-type', 'application/json')
|
|
544
|
+
res.end(JSON.stringify(err.body))
|
|
545
|
+
} else if (typeof err.body === 'string') {
|
|
546
|
+
res.end(err.body)
|
|
547
|
+
} else if (Buffer.isBuffer(err.body)) {
|
|
548
|
+
res.end(err.body)
|
|
549
|
+
} else {
|
|
550
|
+
res.end()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (statusCode < 500) {
|
|
554
|
+
reqLogger.warn({ res, err, elapsedTime }, 'request failed')
|
|
555
|
+
} else {
|
|
556
|
+
reqLogger.error({ res, err, elapsedTime }, 'request error')
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
if (req.aborted || !res.writableEnded || err.name === 'AbortError') {
|
|
560
|
+
reqLogger.debug({ res, err, elapsedTime }, 'request aborted')
|
|
561
|
+
} else if (statusCode < 500) {
|
|
562
|
+
reqLogger.warn({ res, err, elapsedTime }, 'request failed')
|
|
563
|
+
} else {
|
|
564
|
+
reqLogger.error({ res, err, elapsedTime }, 'request error')
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} finally {
|
|
568
|
+
pendingSet.delete(ctx)
|
|
569
|
+
|
|
570
|
+
if (!res.writableEnded) {
|
|
571
|
+
res.destroy()
|
|
572
|
+
logger.debug('request destroyed')
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** @deprecated */
|
|
573
578
|
export function createServer(options, ctx, middleware) {
|
|
574
579
|
middleware = Array.isArray(middleware) ? middleware : [middleware]
|
|
575
580
|
middleware = fp.values(middleware)
|
|
@@ -598,6 +603,7 @@ export function createServer(options, ctx, middleware) {
|
|
|
598
603
|
return server
|
|
599
604
|
}
|
|
600
605
|
|
|
606
|
+
/** @deprecated */
|
|
601
607
|
export async function upgrade(ctx, next) {
|
|
602
608
|
const { req, res, socket = res, logger } = ctx
|
|
603
609
|
|
|
@@ -674,6 +680,7 @@ export async function upgrade(ctx, next) {
|
|
|
674
680
|
}
|
|
675
681
|
}
|
|
676
682
|
|
|
683
|
+
/** @deprecated */
|
|
677
684
|
export function isConnectionError(err) {
|
|
678
685
|
// AWS compat.
|
|
679
686
|
const statusCode = err?.statusCode ?? err?.$metadata?.httpStatusCode
|
|
@@ -696,6 +703,7 @@ export function isConnectionError(err) {
|
|
|
696
703
|
: false
|
|
697
704
|
}
|
|
698
705
|
|
|
706
|
+
/** @deprecated */
|
|
699
707
|
export function defaultDelay(err, retryCount, options) {
|
|
700
708
|
const { signal, logger = null } = options ?? {}
|
|
701
709
|
if (isConnectionError(err)) {
|
|
@@ -708,6 +716,7 @@ export function defaultDelay(err, retryCount, options) {
|
|
|
708
716
|
}
|
|
709
717
|
}
|
|
710
718
|
|
|
719
|
+
/** @deprecated */
|
|
711
720
|
export async function retry(fn, options) {
|
|
712
721
|
const { maxRetries = 8, count = maxRetries, delay = defaultDelay, signal } = options ?? {}
|
|
713
722
|
|
package/package.json
CHANGED
package/serializers.js
CHANGED
|
@@ -217,11 +217,21 @@ Object.defineProperty(pinoErrProto, rawSymbol, {
|
|
|
217
217
|
function errSerializer(err) {
|
|
218
218
|
if (Array.isArray(err)) {
|
|
219
219
|
return err.length === 0 ? undefined : errSerializer({ message: '', errors: err })
|
|
220
|
-
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!isErrorLike(err)) {
|
|
221
223
|
return undefined
|
|
222
|
-
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const errors = Array.isArray(err?.errors)
|
|
227
|
+
? err.errors.map((err) => errSerializer(err)).filter(Boolean)
|
|
228
|
+
: null
|
|
229
|
+
|
|
230
|
+
if (err.message === '' && errors != null && errors.length === 0) {
|
|
223
231
|
return undefined
|
|
224
|
-
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (err.message === '' && errors != null && errors.length === 1) {
|
|
225
235
|
return errSerializer(err.errors[0])
|
|
226
236
|
}
|
|
227
237
|
|
|
@@ -233,8 +243,8 @@ function errSerializer(err) {
|
|
|
233
243
|
_err.message = err.message
|
|
234
244
|
_err.stack = err.stack
|
|
235
245
|
|
|
236
|
-
if (
|
|
237
|
-
_err.aggregateErrors =
|
|
246
|
+
if (errors != null) {
|
|
247
|
+
_err.aggregateErrors = errors
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen)) {
|
package/trace.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { threadId } from 'node:worker_threads'
|
|
2
|
-
import { Pool } from 'undici'
|
|
2
|
+
import { request, Pool } from '@nxtedition/nxt-undici'
|
|
3
3
|
import fp from 'lodash/fp.js'
|
|
4
4
|
|
|
5
5
|
const maxInt = 2147483647
|
|
@@ -23,20 +23,20 @@ export function makeTrace({
|
|
|
23
23
|
|
|
24
24
|
let pending = 0
|
|
25
25
|
|
|
26
|
+
const flushInterval = setInterval(flushTraces, 10e3)
|
|
27
|
+
|
|
26
28
|
const client = new Pool(Array.isArray(url) ? url[0] : url, {
|
|
27
29
|
keepAliveTimeout: 10 * 60e3,
|
|
28
30
|
pipelining: 4,
|
|
29
31
|
connections: 4,
|
|
30
32
|
})
|
|
31
33
|
|
|
32
|
-
const flushInterval = setInterval(flushTraces, 10e3)
|
|
33
|
-
|
|
34
34
|
destroyers?.push(async () => {
|
|
35
35
|
clearInterval(flushInterval)
|
|
36
|
+
await client.close()
|
|
36
37
|
while (traceData) {
|
|
37
38
|
await flushTraces()
|
|
38
39
|
}
|
|
39
|
-
await client.close()
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
let traceData = ''
|
|
@@ -50,18 +50,16 @@ export function makeTrace({
|
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
52
|
pending += data.length
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
.then(({ body }) => body.dump())
|
|
53
|
+
request({
|
|
54
|
+
path: '/_bulk',
|
|
55
|
+
method: 'POST',
|
|
56
|
+
idempotent: true,
|
|
57
|
+
headers: HEADERS,
|
|
58
|
+
headersTimeout: 60e3,
|
|
59
|
+
bodyTimeout: 60e3,
|
|
60
|
+
body: data,
|
|
61
|
+
dispatcher: client,
|
|
62
|
+
}).then(({ body }) => body.dump())
|
|
65
63
|
} catch (err) {
|
|
66
64
|
logger.error({ err }, 'trace failed')
|
|
67
65
|
} finally {
|