@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 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
- logger.warn({ err }, 'failed to create ./nxt dir')
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 = this.url.search.length > 1 ? querystring.parse(this.url.search.slice(1)) : {}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "23.3.18",
3
+ "version": "23.3.20",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -0,0 +1,6 @@
1
+ import type { ObservableInput, OperatorFunction } from 'rxjs'
2
+
3
+ export default function combineMap<T, R>(
4
+ project: (value: T) => ObservableInput<R>,
5
+ equals?: (a: T, b: T) => boolean,
6
+ ): OperatorFunction<T[], R[]>
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
- } else if (!isErrorLike(err)) {
220
+ }
221
+
222
+ if (!isErrorLike(err)) {
221
223
  return undefined
222
- } else if (err.message === '' && Array.isArray(err.errors) && err.errors.length === 0) {
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
- } else if (err.message === '' && Array.isArray(err.errors) && err.errors.length === 1) {
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 (Array.isArray(err.errors)) {
237
- _err.aggregateErrors = err.errors.map((err) => errSerializer(err))
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
- await client
54
- .request({
55
- throwOnError: true,
56
- path: '/_bulk',
57
- method: 'POST',
58
- idempotent: true,
59
- headers: HEADERS,
60
- headersTimeout: 60e3,
61
- bodyTimeout: 60e3,
62
- body: data,
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 {