@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 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.13.0",
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 ?? -1,
75
- headers: timing.headers ?? -1,
76
- data: timing.data ?? -1,
77
- end: timing.end ?? timing.complete ?? timing.error ?? -1,
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.target,
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
- ures: (ures) =>
134
- ures && {
135
- id: ures.id || getHeader(ures, 'request-id') || getHeader(ures.req, 'request-id'),
136
- userAgent: ures.userAgent ?? getHeader(ures, 'user-agent'),
137
- timing: getTiming(ures),
138
- statusCode: ures.statusCode ?? ures.status,
139
- bytesRead: ures.bytesRead,
140
- bytesReadPerSecond:
141
- ures.bytesReadPerSecond ??
142
- (ures.timing && ures.timing.data > 0 && ures.timing.end > 0
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
- body: typeof ures.body === 'string' ? ures.body : undefined,
146
- headers: getHeaders(ures),
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()