@nxtedition/lib 23.13.0 → 23.14.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/fixed-queue.js +113 -0
- package/package.json +4 -2
- package/serializers.js +52 -41
- package/yield.js +58 -0
package/fixed-queue.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Extracted from node/lib/internal/fixed_queue.js
|
|
2
|
+
|
|
3
|
+
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
|
|
4
|
+
const kSize = 2048
|
|
5
|
+
const kMask = kSize - 1
|
|
6
|
+
|
|
7
|
+
// The FixedQueue is implemented as a singly-linked list of fixed-size
|
|
8
|
+
// circular buffers. It looks something like this:
|
|
9
|
+
//
|
|
10
|
+
// head tail
|
|
11
|
+
// | |
|
|
12
|
+
// v v
|
|
13
|
+
// +-----------+ <-----\ +-----------+ <------\ +-----------+
|
|
14
|
+
// | [null] | \----- | next | \------- | next |
|
|
15
|
+
// +-----------+ +-----------+ +-----------+
|
|
16
|
+
// | item | <-- bottom | item | <-- bottom | [empty] |
|
|
17
|
+
// | item | | item | | [empty] |
|
|
18
|
+
// | item | | item | | [empty] |
|
|
19
|
+
// | item | | item | | [empty] |
|
|
20
|
+
// | item | | item | bottom --> | item |
|
|
21
|
+
// | item | | item | | item |
|
|
22
|
+
// | ... | | ... | | ... |
|
|
23
|
+
// | item | | item | | item |
|
|
24
|
+
// | item | | item | | item |
|
|
25
|
+
// | [empty] | <-- top | item | | item |
|
|
26
|
+
// | [empty] | | item | | item |
|
|
27
|
+
// | [empty] | | [empty] | <-- top top --> | [empty] |
|
|
28
|
+
// +-----------+ +-----------+ +-----------+
|
|
29
|
+
//
|
|
30
|
+
// Or, if there is only one circular buffer, it looks something
|
|
31
|
+
// like either of these:
|
|
32
|
+
//
|
|
33
|
+
// head tail head tail
|
|
34
|
+
// | | | |
|
|
35
|
+
// v v v v
|
|
36
|
+
// +-----------+ +-----------+
|
|
37
|
+
// | [null] | | [null] |
|
|
38
|
+
// +-----------+ +-----------+
|
|
39
|
+
// | [empty] | | item |
|
|
40
|
+
// | [empty] | | item |
|
|
41
|
+
// | item | <-- bottom top --> | [empty] |
|
|
42
|
+
// | item | | [empty] |
|
|
43
|
+
// | [empty] | <-- top bottom --> | item |
|
|
44
|
+
// | [empty] | | item |
|
|
45
|
+
// +-----------+ +-----------+
|
|
46
|
+
//
|
|
47
|
+
// Adding a value means moving `top` forward by one, removing means
|
|
48
|
+
// moving `bottom` forward by one. After reaching the end, the queue
|
|
49
|
+
// wraps around.
|
|
50
|
+
//
|
|
51
|
+
// When `top === bottom` the current queue is empty and when
|
|
52
|
+
// `top + 1 === bottom` it's full. This wastes a single space of storage
|
|
53
|
+
// but allows much quicker checks.
|
|
54
|
+
|
|
55
|
+
class FixedCircularBuffer {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.bottom = 0
|
|
58
|
+
this.top = 0
|
|
59
|
+
this.list = new Array(kSize)
|
|
60
|
+
this.next = null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
isEmpty() {
|
|
64
|
+
return this.top === this.bottom
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isFull() {
|
|
68
|
+
return ((this.top + 1) & kMask) === this.bottom
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
push(data) {
|
|
72
|
+
this.list[this.top] = data
|
|
73
|
+
this.top = (this.top + 1) & kMask
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
shift() {
|
|
77
|
+
const nextItem = this.list[this.bottom]
|
|
78
|
+
if (nextItem === undefined) return null
|
|
79
|
+
this.list[this.bottom] = undefined
|
|
80
|
+
this.bottom = (this.bottom + 1) & kMask
|
|
81
|
+
return nextItem
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class FixedQueue {
|
|
86
|
+
constructor() {
|
|
87
|
+
this.head = this.tail = new FixedCircularBuffer()
|
|
88
|
+
this.size = 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isEmpty() {
|
|
92
|
+
return this.head.isEmpty()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
push(data) {
|
|
96
|
+
if (this.head.isFull()) {
|
|
97
|
+
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
|
98
|
+
// and sets it as the new main queue.
|
|
99
|
+
this.head = this.head.next = new FixedCircularBuffer()
|
|
100
|
+
}
|
|
101
|
+
this.head.push(data)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
shift() {
|
|
105
|
+
const tail = this.tail
|
|
106
|
+
const next = tail.shift()
|
|
107
|
+
if (tail.isEmpty() && tail.next !== null) {
|
|
108
|
+
// If there is another queue, it forms the new tail.
|
|
109
|
+
this.tail = tail.next
|
|
110
|
+
}
|
|
111
|
+
return next
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/lib",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.14.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Robert Nagy <robert.nagy@boffins.se>",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"rxjs/*",
|
|
11
11
|
"util/*",
|
|
12
12
|
"cache.js",
|
|
13
|
+
"fixed-queue.js",
|
|
13
14
|
"http-client.js",
|
|
14
15
|
"subtract-ranges.js",
|
|
15
16
|
"serializers.js",
|
|
@@ -44,7 +45,8 @@
|
|
|
44
45
|
"transcript.js",
|
|
45
46
|
"docker-secrets.js",
|
|
46
47
|
"wordwrap.js",
|
|
47
|
-
"under-pressure.js"
|
|
48
|
+
"under-pressure.js",
|
|
49
|
+
"yield.js"
|
|
48
50
|
],
|
|
49
51
|
"scripts": {
|
|
50
52
|
"prepublishOnly": "pinst --disable",
|
package/serializers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SIGNALS } from './platform.js'
|
|
2
2
|
import { parseHeaders } from '@nxtedition/nxt-undici'
|
|
3
|
+
import requestTarget from 'request-target'
|
|
3
4
|
|
|
4
5
|
function getHeader(obj, key) {
|
|
5
6
|
return !obj || !key
|
|
@@ -71,13 +72,28 @@ function getTiming(obj) {
|
|
|
71
72
|
return undefined
|
|
72
73
|
}
|
|
73
74
|
return {
|
|
74
|
-
connect: timing.connect
|
|
75
|
-
headers: timing.headers
|
|
76
|
-
data: timing.data
|
|
77
|
-
end: timing.end
|
|
75
|
+
connect: timing.connect > 0 ? Math.ceil(timing.connect * 1e3) : undefined,
|
|
76
|
+
headers: timing.headers > 0 ? Math.ceil(timing.headers * 1e3) : undefined,
|
|
77
|
+
data: timing.data > 0 ? Math.ceil(timing.data * 1e3) : undefined,
|
|
78
|
+
end: timing.end > 0 ? Math.ceil(timing.end * 1e3) : undefined,
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function getTarget(obj) {
|
|
83
|
+
if (!obj) {
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
if (obj.target) {
|
|
87
|
+
return obj.target
|
|
88
|
+
}
|
|
89
|
+
if (typeof obj.url === 'string') {
|
|
90
|
+
// TODO(fix): What if url is a full url?
|
|
91
|
+
return requestTarget({ url: obj.url, headers: obj.headers ?? {}, socket: obj.socket })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TODO(fix): What if url is instanceof URL?
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
export default {
|
|
82
98
|
err: (err) => errSerializer(err),
|
|
83
99
|
error: (err) => errSerializer(err),
|
|
@@ -91,37 +107,16 @@ export default {
|
|
|
91
107
|
remoteAddress: socket.remoteAddress || undefined,
|
|
92
108
|
headers: socket.headers,
|
|
93
109
|
},
|
|
94
|
-
res: (res) =>
|
|
95
|
-
res && {
|
|
96
|
-
id: res.id ?? res.req?.id ?? getHeader(res, 'request-id') ?? getHeader(res.req, 'request-id'),
|
|
97
|
-
url: res.req?.url,
|
|
98
|
-
method: res.req?.method,
|
|
99
|
-
headers: getHeaders(res),
|
|
100
|
-
statusCode: res.statusCode || res.status,
|
|
101
|
-
timing: getTiming(res),
|
|
102
|
-
bytesWritten: res.bytesWritten,
|
|
103
|
-
bytesWrittenPerSecond:
|
|
104
|
-
res.bytesWrittenPerSecond ??
|
|
105
|
-
(res.timing?.complete > 0 && res.bytesWritten
|
|
106
|
-
? (res.bytesWritten * 1e3) / res.timing.complete
|
|
107
|
-
: undefined),
|
|
108
|
-
headersSent: res.headersSent,
|
|
109
|
-
aborted: res.aborted,
|
|
110
|
-
closed: res.closed,
|
|
111
|
-
destroyed: res.destroyed,
|
|
112
|
-
},
|
|
113
110
|
req: (req) =>
|
|
114
111
|
req && {
|
|
115
|
-
id: req.id || getHeader(req, 'request-id'),
|
|
116
|
-
url: req.url,
|
|
117
112
|
method: req.method,
|
|
118
113
|
headers: getHeaders(req),
|
|
119
|
-
target: req
|
|
114
|
+
target: getTarget(req),
|
|
120
115
|
timing: getTiming(req),
|
|
121
|
-
bytesRead: req.bytesRead,
|
|
116
|
+
bytesRead: req.bytesRead >= 0 ? req.bytesRead : undefined,
|
|
122
117
|
bytesReadPerSecond:
|
|
123
118
|
req.bytesReadPerSecond ??
|
|
124
|
-
(req.timing?.complete > 0 && req.bytesRead
|
|
119
|
+
(req.timing?.complete > 0 && req.bytesRead > 0
|
|
125
120
|
? (req.bytesRead * 1e3) / req.timing.complete
|
|
126
121
|
: undefined),
|
|
127
122
|
remoteAddress: req.socket?.remoteAddress,
|
|
@@ -130,20 +125,21 @@ export default {
|
|
|
130
125
|
closed: req.closed,
|
|
131
126
|
destroyed: req.destroyed,
|
|
132
127
|
},
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
timing: getTiming(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
? (ures.bytesRead * 1e3) / (ures.timing.end - ures.timing.data)
|
|
128
|
+
res: (res) =>
|
|
129
|
+
res && {
|
|
130
|
+
headers: getHeaders(res),
|
|
131
|
+
statusCode: res.statusCode || res.status,
|
|
132
|
+
timing: getTiming(res),
|
|
133
|
+
bytesWritten: res.bytesWritten >= 0 ? res.bytesWritten : undefined,
|
|
134
|
+
bytesWrittenPerSecond:
|
|
135
|
+
res.bytesWrittenPerSecond ??
|
|
136
|
+
(res.timing?.complete > 0 && res.bytesWritten > 0
|
|
137
|
+
? (res.bytesWritten * 1e3) / res.timing.complete
|
|
144
138
|
: undefined),
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
headersSent: res.headersSent,
|
|
140
|
+
aborted: res.aborted,
|
|
141
|
+
closed: res.closed,
|
|
142
|
+
destroyed: res.destroyed,
|
|
147
143
|
},
|
|
148
144
|
ureq: (ureq) =>
|
|
149
145
|
ureq && {
|
|
@@ -162,6 +158,21 @@ export default {
|
|
|
162
158
|
headers: getHeaders(ureq),
|
|
163
159
|
query: ureq.query,
|
|
164
160
|
},
|
|
161
|
+
ures: (ures) =>
|
|
162
|
+
ures && {
|
|
163
|
+
id: ures.id || getHeader(ures, 'request-id') || getHeader(ures.req, 'request-id'),
|
|
164
|
+
userAgent: ures.userAgent ?? getHeader(ures, 'user-agent'),
|
|
165
|
+
timing: getTiming(ures),
|
|
166
|
+
statusCode: ures.statusCode ?? ures.status,
|
|
167
|
+
bytesRead: ures.bytesRead,
|
|
168
|
+
bytesReadPerSecond:
|
|
169
|
+
ures.bytesReadPerSecond ??
|
|
170
|
+
(ures.timing && ures.timing.data > 0 && ures.timing.end > 0
|
|
171
|
+
? (ures.bytesRead * 1e3) / (ures.timing.end - ures.timing.data)
|
|
172
|
+
: undefined),
|
|
173
|
+
body: typeof ures.body === 'string' ? ures.body : undefined,
|
|
174
|
+
headers: getHeaders(ures),
|
|
175
|
+
},
|
|
165
176
|
}
|
|
166
177
|
|
|
167
178
|
// TODO (fix): Merge with errros/serializeError.
|
package/yield.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FixedQueue } from './fixed-queue.js'
|
|
2
|
+
|
|
3
|
+
const yieldTimeout = 50
|
|
4
|
+
const yieldQueue = new FixedQueue()
|
|
5
|
+
let yieldScheduled = false
|
|
6
|
+
let yieldTime = performance.now()
|
|
7
|
+
let yieldActive = false
|
|
8
|
+
|
|
9
|
+
export function maybeYield(opts, callback) {
|
|
10
|
+
if (callback != null && typeof callback !== 'function') {
|
|
11
|
+
throw new TypeError('callback must be a function')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return performance.now() - yieldTime < yieldTimeout ? null : doYield(opts, callback)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function doYield(opts, callback) {
|
|
18
|
+
if (!yieldScheduled) {
|
|
19
|
+
yieldScheduled = true
|
|
20
|
+
setImmediate(dispatchYield)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (callback) {
|
|
24
|
+
yieldQueue.push(callback)
|
|
25
|
+
return true
|
|
26
|
+
} else {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
yieldQueue.push(resolve)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function dispatchYield() {
|
|
34
|
+
if (yieldActive) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
yieldActive = true
|
|
39
|
+
yieldTime = performance.now()
|
|
40
|
+
|
|
41
|
+
while (!yieldQueue.isEmpty()) {
|
|
42
|
+
const resolve = yieldQueue.shift()
|
|
43
|
+
|
|
44
|
+
resolve(null)
|
|
45
|
+
|
|
46
|
+
if (performance.now() - yieldTime > yieldTimeout) {
|
|
47
|
+
setImmediate(dispatchYield)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
yieldActive = false
|
|
53
|
+
yieldScheduled = false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setInterval(() => {
|
|
57
|
+
yieldTime = performance.now()
|
|
58
|
+
}, 500).unref()
|