@nxtedition/lib 23.5.14 → 23.6.1

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/http.js CHANGED
@@ -33,101 +33,128 @@ function onTimeout() {
33
33
  this.destroy((timeoutError ??= new createError.RequestTimeout()))
34
34
  }
35
35
 
36
+ // TODO (fix): Make custom ServerRequest class with the properties
37
+ // that is currently deprecated by Context.
38
+
36
39
  export class Context {
37
40
  #id
38
41
  #userAgent
39
42
  #req
40
43
  #res
41
44
  #ac
42
- #url
45
+ #target
43
46
  #logger
44
47
  #query
45
48
 
46
49
  constructor(req, res, logger) {
47
50
  assert(req)
48
51
  assert(res)
49
- assert(logger)
50
-
51
- const id = req.headers['request-id'] || req.headers['Request-Id']
52
52
 
53
- this.#id = (id ? `${id},` : '') + genReqId()
53
+ this.#id = req.id || req.headers['request-id'] || req.headers['Request-Id'] || genReqId()
54
54
  this.#userAgent = req.headers['user-agent'] || req.headers['User-Agent'] || ''
55
55
  this.#req = req
56
56
  this.#res = res
57
- this.#logger = logger.child({ reqId: this.#id, url: req.url, userAgent: this.#userAgent })
57
+ this.#logger = logger.child({ req: { id: this.#req.id } })
58
+ this.#target = requestTarget(this.#req)
59
+ this.#query = undefined
60
+
61
+ // TODO (fix): This should not be necessary...
62
+ req.target ??= this.#target
63
+ req.userAgent ??= this.#userAgent
64
+ req.id ??= this.#id
65
+ req.logger ??= this.#logger
58
66
  }
59
67
 
60
68
  get id() {
69
+ if (this.#req.id) {
70
+ return this.#req.id
71
+ }
72
+
61
73
  return this.#id
62
74
  }
63
75
 
76
+ get logger() {
77
+ return this.#logger
78
+ }
79
+
80
+ get req() {
81
+ return this.#req
82
+ }
83
+
84
+ get res() {
85
+ return this.#res
86
+ }
87
+
88
+ get [kAbortController]() {
89
+ return this.#ac
90
+ }
91
+
92
+ get signal() {
93
+ this.#ac ??= new AbortController()
94
+ return this.#ac.signal
95
+ }
96
+
97
+ /** @deprecated */
64
98
  get userAgent() {
99
+ if (this.#req.userAgent) {
100
+ return this.#req.userAgent
101
+ }
102
+
65
103
  return this.#userAgent
66
104
  }
67
105
 
106
+ /** @deprecated */
68
107
  get method() {
69
108
  return this.#req.method
70
109
  }
71
110
 
111
+ /** @deprecated */
72
112
  get url() {
73
- if (this.#url === undefined) {
74
- this.#url = requestTarget(this.#req)
113
+ if (this.#req.target) {
114
+ return this.#req.target
75
115
  }
76
116
 
77
- if (!this.#url) {
117
+ this.#target ??= requestTarget(this.#req)
118
+
119
+ if (!this.#target) {
78
120
  throw new createError.BadRequest('invalid url')
79
121
  }
80
122
 
81
- return this.#url
123
+ return this.#target
82
124
  }
83
125
 
126
+ /** @deprecated */
84
127
  set url(value) {
85
- if (value instanceof URL) {
86
- value = value.toString()
87
- }
88
-
89
128
  if (typeof value !== 'string') {
90
129
  throw new Error('url must be a string')
91
130
  }
92
131
 
93
132
  this.#req.url = value
94
- this.#url = undefined
133
+ this.#target = undefined
95
134
  this.#query = undefined
96
135
  }
97
136
 
137
+ /** @deprecated */
98
138
  get query() {
99
- if (this.#query === undefined) {
100
- this.#query =
101
- this.url.search.length > 1 ? Object.freeze(querystring.parse(this.url.search.slice(1))) : {}
139
+ if (this.#req.query) {
140
+ return this.#req.query
102
141
  }
103
- return this.#query
104
- }
105
-
106
- set query(value) {}
107
142
 
108
- get logger() {
109
- return this.#logger
110
- }
143
+ this.#query ??=
144
+ this.#target.search.length > 1
145
+ ? Object.freeze(querystring.parse(this.#target.search.slice(1)))
146
+ : {}
111
147
 
112
- set logger(val) {
113
- this.#logger = val
114
- }
115
-
116
- get req() {
117
- return this.#req
118
- }
119
-
120
- get res() {
121
- return this.#res
148
+ return this.#query
122
149
  }
123
150
 
124
- get [kAbortController]() {
125
- return this.#ac
126
- }
151
+ /** @deprecated */
152
+ set query(value) {}
127
153
 
128
- get signal() {
129
- this.#ac ??= new AbortController()
130
- return this.#ac.signal
154
+ /** @deprecated */
155
+ set logger(logger) {
156
+ this.#logger = logger.child({ req: { id: this.#req.id } })
157
+ this.#req.logger = this.#logger
131
158
  }
132
159
  }
133
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "23.5.14",
3
+ "version": "23.6.1",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -43,7 +43,8 @@
43
43
  "timeline.js",
44
44
  "transcript.js",
45
45
  "docker-secrets.js",
46
- "wordwrap.js"
46
+ "wordwrap.js",
47
+ "under-pressure.js"
47
48
  ],
48
49
  "scripts": {
49
50
  "prepublishOnly": "pinst --disable",
package/serializers.js CHANGED
@@ -2,7 +2,9 @@ import { SIGNALS } from './platform.js'
2
2
  import { parseHeaders } from '@nxtedition/nxt-undici'
3
3
 
4
4
  function getHeader(obj, key) {
5
- return obj?.headers?.get?.(key) || obj?.getHeader?.(key) || obj?.headers?.[key]
5
+ return !obj || !key
6
+ ? undefined
7
+ : obj?.headers?.get?.(key) || obj?.getHeader?.(key) || obj?.headers?.[key]
6
8
  }
7
9
 
8
10
  function getHeaders(obj) {
@@ -91,8 +93,8 @@ export default {
91
93
  },
92
94
  res: (res) =>
93
95
  res && {
94
- id: res.id || res.req?.id || getHeader(res, 'request-id') || getHeader(res.req, 'request-id'),
95
- userAgent: res.userAgent ?? getHeader(res, 'user-agent'),
96
+ id: res.id ?? res.req?.id ?? getHeader(res, 'request-id') ?? getHeader(res.req, 'request-id'),
97
+ userAgent: res.userAgent ?? res.req?.userAgent ?? getHeader(res.req, 'user-agent'),
96
98
  timing: getTiming(res),
97
99
  statusCode: res.statusCode || res.status,
98
100
  bytesWritten: res.bytesWritten,
@@ -112,14 +114,11 @@ export default {
112
114
  id: req.id || getHeader(req, 'request-id'),
113
115
  userAgent: req.userAgent ?? getHeader(req, 'user-agent'),
114
116
  timing: getTiming(req),
117
+ target: req.target,
115
118
  method: req.method,
116
119
  url: req.url,
117
- scheme: req.scheme,
118
- host: req.host,
119
- port: req.port,
120
- path: req.path,
121
- query: req.query,
122
120
  headers: getHeaders(req),
121
+ query: req.query,
123
122
  bytesRead: req.bytesRead,
124
123
  bytesReadPerSecond:
125
124
  req.bytesReadPerSecond ??
@@ -152,8 +151,8 @@ export default {
152
151
  id: ureq.id || getHeader(ureq, 'request-id'),
153
152
  userAgent: ureq.userAgent ?? getHeader(ureq, 'user-agent'),
154
153
  timing: getTiming(ureq),
155
- method: ureq.method,
156
154
  url: getUrl(ureq),
155
+ method: ureq.method,
157
156
  body: typeof ureq.body === 'string' ? ureq.body : undefined,
158
157
  bytesWritten: ureq.bytesWritten,
159
158
  bytesReadPerSecond:
@@ -0,0 +1,105 @@
1
+ // Based on: https://github.com/fastify/under-pressure/blob/main/index.js
2
+ import { monitorEventLoopDelay } from 'node:perf_hooks'
3
+
4
+ function getSampleInterval(value, eventLoopResolution) {
5
+ const sampleInterval = value || 1000
6
+
7
+ return Math.max(eventLoopResolution, sampleInterval)
8
+ }
9
+
10
+ export default function (opts = {}) {
11
+ const resolution = 10
12
+ const sampleInterval = getSampleInterval(opts.sampleInterval, resolution)
13
+ const maxEventLoopDelay = opts.maxEventLoopDelay || 0
14
+ const maxHeapUsedBytes = opts.maxHeapUsedBytes || 0
15
+ const maxRssBytes = opts.maxRssBytes || 0
16
+ const healthCheck = opts.healthCheck || false
17
+ const maxEventLoopUtilization = opts.maxEventLoopUtilization || 0
18
+
19
+ const checkMaxEventLoopDelay = maxEventLoopDelay > 0
20
+ const checkMaxHeapUsedBytes = maxHeapUsedBytes > 0
21
+ const checkMaxRssBytes = maxRssBytes > 0
22
+ const checkMaxEventLoopUtilization = maxEventLoopUtilization > 0
23
+
24
+ let heapUsed = 0
25
+ let rssBytes = 0
26
+ let eventLoopDelay = 0
27
+ let elu
28
+ let eventLoopUtilized = 0
29
+
30
+ const histogram = monitorEventLoopDelay({ resolution })
31
+ histogram.enable()
32
+
33
+ if (performance.eventLoopUtilization) {
34
+ elu = performance.eventLoopUtilization()
35
+ }
36
+
37
+ const timer = setTimeout(beginMemoryUsageUpdate, sampleInterval)
38
+ timer.unref()
39
+
40
+ if (
41
+ checkMaxEventLoopUtilization === false &&
42
+ checkMaxEventLoopDelay === false &&
43
+ checkMaxHeapUsedBytes === false &&
44
+ checkMaxRssBytes === false &&
45
+ healthCheck === false
46
+ ) {
47
+ return
48
+ }
49
+
50
+ function updateEventLoopDelay() {
51
+ eventLoopDelay = Math.max(0, histogram.mean / 1e6 - resolution)
52
+ if (Number.isNaN(eventLoopDelay)) eventLoopDelay = Infinity
53
+ histogram.reset()
54
+ }
55
+
56
+ function updateEventLoopUtilization() {
57
+ if (elu) {
58
+ eventLoopUtilized = performance.eventLoopUtilization(elu).utilization
59
+ } else {
60
+ eventLoopUtilized = 0
61
+ }
62
+ }
63
+
64
+ function beginMemoryUsageUpdate() {
65
+ updateMemoryUsage()
66
+ timer.refresh()
67
+ }
68
+
69
+ function updateMemoryUsage() {
70
+ const mem = process.memoryUsage()
71
+ heapUsed = mem.heapUsed
72
+ rssBytes = mem.rss
73
+ updateEventLoopDelay()
74
+ updateEventLoopUtilization()
75
+ }
76
+
77
+ return {
78
+ isUnderPressure() {
79
+ if (checkMaxEventLoopDelay && eventLoopDelay > maxEventLoopDelay) {
80
+ return true
81
+ }
82
+
83
+ if (checkMaxHeapUsedBytes && heapUsed > maxHeapUsedBytes) {
84
+ return true
85
+ }
86
+
87
+ if (checkMaxRssBytes && rssBytes > maxRssBytes) {
88
+ return true
89
+ }
90
+
91
+ return checkMaxEventLoopUtilization && eventLoopUtilized > maxEventLoopUtilization
92
+ },
93
+ memoryUsage() {
94
+ return {
95
+ eventLoopDelay,
96
+ rssBytes,
97
+ heapUsed,
98
+ eventLoopUtilized,
99
+ }
100
+ },
101
+ [Symbol.dispose]() {
102
+ clearTimeout(timer)
103
+ },
104
+ }
105
+ }