@nxtedition/lib 26.4.5 → 26.4.7

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
@@ -129,8 +129,26 @@ export function makeApp(appConfig, onTerminate) {
129
129
  const serviceModule = appConfig.module ?? 'main'
130
130
  const serviceWorkerId = appConfig.workerId ?? threadId
131
131
  const serviceInstanceId =
132
- // process.env.name is the pm2 name of the process
133
- appConfig.instanceId ?? appConfig.containerId ?? process.env.name ?? os.hostname()
132
+ appConfig.instanceId ??
133
+ appConfig.containerId ??
134
+ (() => {
135
+ if (!isProduction) {
136
+ // We are not running in docker, so we use pid as instance id.
137
+ return `pid${process.pid}`
138
+ }
139
+
140
+ try {
141
+ // Normally we have the container ID as hostname, but that can be changed
142
+ // so we read it from mountinfo instead. This is not 100% reliable as it
143
+ // depends on the container runtime.
144
+ return fs
145
+ .readFileSync('/proc/self/mountinfo', { encoding: 'utf8' })
146
+ .match(/[/]([0-9a-f]{64})[/]hostname/)[1]
147
+ .slice(0, 12)
148
+ } catch {}
149
+
150
+ return os.hostname()
151
+ })()
134
152
 
135
153
  if (!serviceName) {
136
154
  throw new Error('Service name is required')
@@ -141,7 +159,7 @@ export function makeApp(appConfig, onTerminate) {
141
159
  (serviceName &&
142
160
  `${serviceName}/${
143
161
  serviceVersion || '*'
144
- } (module:${serviceModule}; instance:${serviceInstanceId}) worker:${serviceWorkerId} Node/${process.version}`) ??
162
+ } (module:${serviceModule}; instance:${serviceInstanceId}; worker:${serviceWorkerId}) Node/${process.version}`) ??
145
163
  null)
146
164
 
147
165
  if (isMainThread && serviceName) {
package/http.js CHANGED
@@ -423,6 +423,7 @@ export class ServerResponse extends http.ServerResponse {
423
423
 
424
424
  /**
425
425
  * @returns {{
426
+ * created: number,
426
427
  * connect: number,
427
428
  * headers: number,
428
429
  * data: number,
@@ -431,6 +432,7 @@ export class ServerResponse extends http.ServerResponse {
431
432
  */
432
433
  get timing() {
433
434
  return {
435
+ created: performance.timeOrigin + this.#created,
434
436
  connect: this.#connect,
435
437
  headers: this.#headers,
436
438
  data: this.#data,
@@ -656,8 +658,18 @@ export class Http2ServerResponse extends http2.Http2ServerResponse {
656
658
  #data = -1
657
659
  #end = -1
658
660
 
661
+ /**
662
+ * @returns {{
663
+ * created: number,
664
+ * connect: number,
665
+ * headers: number,
666
+ * data: number,
667
+ * end: number
668
+ * }}
669
+ */
659
670
  get timing() {
660
671
  return {
672
+ created: performance.timeOrigin + this.#created,
661
673
  connect: this.#connect,
662
674
  headers: this.#headers,
663
675
  data: this.#data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.4.5",
3
+ "version": "26.4.7",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
package/rxjs/retry.js ADDED
@@ -0,0 +1,112 @@
1
+ import { operate } from 'rxjs/internal/util/lift'
2
+ import { createOperatorSubscriber } from 'rxjs/internal/operators/OperatorSubscriber'
3
+ import { identity, timer } from 'rxjs'
4
+ import { innerFrom } from 'rxjs/internal/observable/innerFrom'
5
+
6
+ // This is from https://github.com/ReactiveX/rxjs/blob/7.x/src/internal/operators/retry.ts
7
+ // But with some custom code added to emit a value on each retry attempt.
8
+ // The default delay function is also changed to use exponential backoff, capped at 1 minute.
9
+ //
10
+ // It uses internals from rxjs, but otherwise the code would have to be modified more heavily.
11
+
12
+ const DEFAULT_DELAY = (err, retryCount) => {
13
+ return timer(Math.min(2 ** (retryCount - 1) * 1000, 60_000))
14
+ }
15
+
16
+ export function retry(configOrCount) {
17
+ let config
18
+ if (configOrCount && typeof configOrCount === 'object') {
19
+ config = configOrCount
20
+ } else {
21
+ config = {
22
+ count: configOrCount,
23
+ }
24
+ }
25
+ const { count = Infinity, delay = DEFAULT_DELAY, resetOnSuccess = false, emitOnRetry } = config
26
+
27
+ return count <= 0
28
+ ? identity
29
+ : operate((source, subscriber) => {
30
+ let soFar = 0
31
+ let innerSub
32
+ const subscribeForRetry = () => {
33
+ let syncUnsub = false
34
+ innerSub = source.subscribe(
35
+ createOperatorSubscriber(
36
+ subscriber,
37
+ (value) => {
38
+ // If we're resetting on success
39
+ if (resetOnSuccess) {
40
+ soFar = 0
41
+ }
42
+ subscriber.next(value)
43
+ },
44
+ // Completions are passed through to consumer.
45
+ undefined,
46
+ (err) => {
47
+ if (soFar++ < count) {
48
+ // We are still under our retry count
49
+ const resub = () => {
50
+ if (innerSub) {
51
+ innerSub.unsubscribe()
52
+ innerSub = null
53
+ subscribeForRetry()
54
+ } else {
55
+ syncUnsub = true
56
+ }
57
+ }
58
+
59
+ // ------- START CUSTOM CODE -------
60
+ if (emitOnRetry) {
61
+ try {
62
+ subscriber.next(emitOnRetry(err, soFar))
63
+ } catch (e) {
64
+ subscriber.error(e)
65
+ return
66
+ }
67
+ }
68
+ // ------- END CUSTOM CODE -------
69
+
70
+ if (delay != null) {
71
+ // The user specified a retry delay.
72
+ // They gave us a number, use a timer, otherwise, it's a function,
73
+ // and we're going to call it to get a notifier.
74
+ const notifier =
75
+ typeof delay === 'number' ? timer(delay) : innerFrom(delay(err, soFar))
76
+ const notifierSubscriber = createOperatorSubscriber(
77
+ subscriber,
78
+ () => {
79
+ // After we get the first notification, we
80
+ // unsubscribe from the notifier, because we don't want anymore
81
+ // and we resubscribe to the source.
82
+ notifierSubscriber.unsubscribe()
83
+ resub()
84
+ },
85
+ () => {
86
+ // The notifier completed without emitting.
87
+ // The author is telling us they want to complete.
88
+ subscriber.complete()
89
+ },
90
+ )
91
+ notifier.subscribe(notifierSubscriber)
92
+ } else {
93
+ // There was no notifier given. Just resub immediately.
94
+ resub()
95
+ }
96
+ } else {
97
+ // We're past our maximum number of retries.
98
+ // Just send along the error.
99
+ subscriber.error(err)
100
+ }
101
+ },
102
+ ),
103
+ )
104
+ if (syncUnsub) {
105
+ innerSub.unsubscribe()
106
+ innerSub = null
107
+ subscribeForRetry()
108
+ }
109
+ }
110
+ subscribeForRetry()
111
+ })
112
+ }
package/serializers.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { SIGNALS } from './platform.js'
2
2
  import { parseHeaders } from '@nxtedition/nxt-undici'
3
3
  import requestTarget from 'request-target'
4
+ import querystring from 'fast-querystring'
4
5
 
5
6
  function getHeader(obj, key) {
6
7
  return !obj || !key
7
8
  ? undefined
8
- : obj.headers?.get?.(key) || obj.getHeader?.(key) || obj.headers?.[key]
9
+ : obj.headers?.[key] || obj.headers?.get?.(key) || obj.getHeader?.(key)
9
10
  }
10
11
 
11
12
  function getHeaders(obj) {
@@ -35,15 +36,15 @@ function getUrl(obj) {
35
36
 
36
37
  const { origin, path, hostname, protocol, port, url } = obj
37
38
 
39
+ if (typeof url === 'string' && url) {
40
+ return url
41
+ }
42
+
38
43
  const href = url?.href
39
44
  if (typeof href === 'string' && href) {
40
45
  return href
41
46
  }
42
47
 
43
- if (typeof url === 'string' && url) {
44
- return url
45
- }
46
-
47
48
  if (origin && (!path || typeof path === 'string')) {
48
49
  if (typeof origin === 'object') {
49
50
  return `${origin.protocol || 'http:'}//${origin.hostname}:${origin.port || { 'http:': 80, 'https:': 443 }[origin.protocol]}${path || ''}`
@@ -81,6 +82,18 @@ function getTarget(obj) {
81
82
  if (typeof obj.url === 'string') {
82
83
  // TODO(fix): What if url is a full url?
83
84
  return requestTarget({ url: obj.url, headers: obj.headers ?? {}, socket: obj.socket })
85
+ } else {
86
+ const { origin, path, hostname, protocol, port, search, query } = obj
87
+ return {
88
+ origin,
89
+ protocol: protocol || undefined,
90
+ hostname: hostname || undefined,
91
+ port: port || undefined,
92
+ pathname: path || undefined,
93
+ search:
94
+ search ||
95
+ (typeof query === 'object' && query != null ? querystring.stringify(query) : undefined),
96
+ }
84
97
  }
85
98
  } catch {
86
99
  return undefined
@@ -104,15 +117,17 @@ export default {
104
117
  },
105
118
  req: (req) =>
106
119
  req && {
120
+ id: req.id || getHeader(req, 'request-id'),
107
121
  method: req.method,
108
- headers: getHeaders(req),
109
122
  target: getTarget(req),
123
+ url: getUrl(req),
124
+ headers: getHeaders(req),
110
125
  timing: req.timing,
111
126
  bytesRead: req.bytesRead >= 0 ? req.bytesRead : undefined,
112
127
  bytesReadPerSecond:
113
128
  req.bytesReadPerSecond ??
114
- (req.timing?.complete > 0 && req.bytesRead > 0
115
- ? (req.bytesRead * 1e3) / req.timing.complete
129
+ (req.timing?.end > 0 && req.bytesRead > 0
130
+ ? (req.bytesRead * 1e3) / req.timing.end
116
131
  : undefined),
117
132
  remoteAddress: req.socket?.remoteAddress,
118
133
  remotePort: req.socket?.remotePort,
@@ -120,45 +135,45 @@ export default {
120
135
  closed: req.closed,
121
136
  destroyed: req.destroyed,
122
137
  },
138
+ ureq: (ureq) =>
139
+ ureq && {
140
+ id: ureq.id || getHeader(ureq, 'request-id'),
141
+ method: ureq.method,
142
+ target: getTarget(ureq),
143
+ url: getUrl(ureq),
144
+ headers: getHeaders(ureq),
145
+ timing: ureq.timing,
146
+ bytesWritten: ureq.bytesWritten,
147
+ bytesReadPerSecond:
148
+ ureq.bytesWrittenPerSecond ??
149
+ (ureq.timing && ureq.timing.data > 0 && ureq.timing.end > 0
150
+ ? (ureq.bytesWritten * 1e3) / (ureq.timing.end - ureq.timing.data)
151
+ : undefined),
152
+ body: typeof ureq.body === 'string' ? ureq.body : undefined,
153
+ },
123
154
  res: (res) =>
124
155
  res && {
156
+ id: res.id || getHeader(res, 'request-id') || getHeader(res.req, 'request-id'),
125
157
  headers: getHeaders(res),
126
158
  statusCode: res.statusCode || res.status,
127
159
  timing: res.timing,
128
160
  bytesWritten: res.bytesWritten >= 0 ? res.bytesWritten : undefined,
129
161
  bytesWrittenPerSecond:
130
162
  res.bytesWrittenPerSecond ??
131
- (res.timing?.complete > 0 && res.bytesWritten > 0
132
- ? (res.bytesWritten * 1e3) / res.timing.complete
163
+ (res.timing?.end > 0 && res.bytesWritten > 0
164
+ ? (res.bytesWritten * 1e3) / res.timing.end
133
165
  : undefined),
134
166
  headersSent: res.headersSent,
135
167
  aborted: res.aborted,
136
168
  closed: res.closed,
137
169
  destroyed: res.destroyed,
138
170
  },
139
- ureq: (ureq) =>
140
- ureq && {
141
- url: getUrl(ureq),
142
- id: ureq.id || getHeader(ureq, 'request-id'),
143
- userAgent: ureq.userAgent ?? getHeader(ureq, 'user-agent'),
144
- timing: ureq.timing,
145
- method: ureq.method,
146
- body: typeof ureq.body === 'string' ? ureq.body : undefined,
147
- bytesWritten: ureq.bytesWritten,
148
- bytesReadPerSecond:
149
- ureq.bytesWrittenPerSecond ??
150
- (ureq.timing && ureq.timing.data > 0 && ureq.timing.end > 0
151
- ? (ureq.bytesWritten * 1e3) / (ureq.timing.end - ureq.timing.data)
152
- : undefined),
153
- headers: getHeaders(ureq),
154
- query: ureq.query,
155
- },
156
171
  ures: (ures) =>
157
172
  ures && {
158
173
  id: ures.id || getHeader(ures, 'request-id') || getHeader(ures.req, 'request-id'),
159
- userAgent: ures.userAgent ?? getHeader(ures, 'user-agent'),
160
- timing: ures.timing,
174
+ headers: getHeaders(ures),
161
175
  statusCode: ures.statusCode ?? ures.status,
176
+ timing: ures.timing,
162
177
  bytesRead: ures.bytesRead,
163
178
  bytesReadPerSecond:
164
179
  ures.bytesReadPerSecond ??
@@ -166,7 +181,6 @@ export default {
166
181
  ? (ures.bytesRead * 1e3) / (ures.timing.end - ures.timing.data)
167
182
  : undefined),
168
183
  body: typeof ures.body === 'string' ? ures.body : undefined,
169
- headers: getHeaders(ures),
170
184
  },
171
185
  }
172
186
 
@@ -214,14 +214,18 @@ export function compareRevSliceSlice(a, b) {
214
214
  let lenA = endA
215
215
  a = a.buffer
216
216
 
217
- if (
218
- !Buffer.isBuffer(a) ||
219
- !Number.isInteger(idxA) ||
220
- idxA < 0 ||
221
- !Number.isInteger(lenA) ||
222
- lenA < 0
223
- ) {
224
- throw new Error('invalid argument: a')
217
+ if (!Buffer.isBuffer(a)) {
218
+ throw Object.assign(new Error('a is not a Buffer.'), {
219
+ data: a?.constructor?.name,
220
+ })
221
+ }
222
+
223
+ if (!Number.isInteger(idxA) || idxA < 0) {
224
+ throw Object.assign(new Error('a has invalid index'), { data: idxA })
225
+ }
226
+
227
+ if (!Number.isInteger(lenA) || lenA < 0) {
228
+ throw Object.assign(new Error('a has invalid length'), { data: lenA })
225
229
  }
226
230
 
227
231
  const endB = b.byteOffset + b.byteLength
@@ -229,14 +233,18 @@ export function compareRevSliceSlice(a, b) {
229
233
  let lenB = endB
230
234
  b = b.buffer
231
235
 
232
- if (
233
- !Buffer.isBuffer(b) ||
234
- !Number.isInteger(idxB) ||
235
- idxB < 0 ||
236
- !Number.isInteger(lenB) ||
237
- lenB < 0
238
- ) {
239
- throw new Error('invalid argument: b')
236
+ if (!Buffer.isBuffer(b)) {
237
+ throw Object.assign(new Error('b is not a Buffer.'), {
238
+ data: b?.constructor?.name,
239
+ })
240
+ }
241
+
242
+ if (!Number.isInteger(idxB) || idxB < 0) {
243
+ throw Object.assign(new Error('b has invalid index'), { data: idxB })
244
+ }
245
+
246
+ if (!Number.isInteger(lenB) || lenB < 0) {
247
+ throw Object.assign(new Error('b has invalid length'), { data: lenB })
240
248
  }
241
249
 
242
250
  // Handle INF-XXXXXXXX