@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 +21 -3
- package/http.js +12 -0
- package/package.json +1 -1
- package/rxjs/retry.js +112 -0
- package/serializers.js +44 -30
- package/util/compare-rev.js +24 -16
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
|
-
|
|
133
|
-
appConfig.
|
|
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}
|
|
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
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?.
|
|
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?.
|
|
115
|
-
? (req.bytesRead * 1e3) / req.timing.
|
|
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?.
|
|
132
|
-
? (res.bytesWritten * 1e3) / res.timing.
|
|
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
|
-
|
|
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
|
|
package/util/compare-rev.js
CHANGED
|
@@ -214,14 +214,18 @@ export function compareRevSliceSlice(a, b) {
|
|
|
214
214
|
let lenA = endA
|
|
215
215
|
a = a.buffer
|
|
216
216
|
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
) {
|
|
224
|
-
throw new Error('invalid
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
) {
|
|
239
|
-
throw new Error('invalid
|
|
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
|