@nxtedition/lib 15.0.34 → 15.0.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "15.0.34",
3
+ "version": "15.0.35",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "files": [
@@ -8,8 +8,6 @@
8
8
  "ass.js",
9
9
  "rxjs/*",
10
10
  "util/*",
11
- "lib/*",
12
- "undici.js",
13
11
  "http-client.js",
14
12
  "subtract-ranges.js",
15
13
  "serializers.js",
@@ -1,237 +0,0 @@
1
- const assert = require('assert')
2
- const createError = require('http-errors')
3
- const xuid = require('xuid')
4
- const undici = require('undici')
5
- const stream = require('stream')
6
- const { parseHeaders } = require('../../http')
7
-
8
- class Readable extends stream.Readable {
9
- constructor({ statusCode, statusMessage, headers, size, ...opts }) {
10
- super(opts)
11
- this.statusCode = statusCode
12
- this.statusMessage = statusMessage
13
- this.headers = headers
14
- this.body = this
15
- this.size = size
16
- }
17
-
18
- async text() {
19
- const dec = new TextDecoder()
20
- let str = ''
21
- for await (const chunk of this) {
22
- if (typeof chunk === 'string') {
23
- str += chunk
24
- } else {
25
- str += dec.decode(chunk, { stream: true })
26
- }
27
- }
28
- // Flush the streaming TextDecoder so that any pending
29
- // incomplete multibyte characters are handled.
30
- str += dec.decode(undefined, { stream: false })
31
- return str
32
- }
33
-
34
- async json() {
35
- return JSON.parse(await this.text())
36
- }
37
-
38
- async arrayBuffer() {
39
- const buffers = []
40
- for await (const chunk of this) {
41
- buffers.push(chunk)
42
- }
43
- return Buffer.concat(buffers)
44
- }
45
-
46
- async buffer() {
47
- return Buffer.from(await this.arrayBuffer())
48
- }
49
-
50
- async dump() {
51
- let n = 0
52
- try {
53
- for await (const chunk of this) {
54
- // do nothing
55
- n += chunk.length
56
- if (n > 128 * 1024) {
57
- break
58
- }
59
- }
60
- } catch {
61
- this.destroy()
62
- }
63
- }
64
- }
65
-
66
- const dispatchers = {
67
- abort: require('./interceptor/abort.js'),
68
- catch: require('./interceptor/catch.js'),
69
- content: require('./interceptor/content.js'),
70
- log: require('./interceptor/log.js'),
71
- redirect: require('./interceptor/redirect.js'),
72
- responseBodyRetry: require('./interceptor/response-body-retry.js'),
73
- responseStatusRetry: require('./interceptor/response-status-retry.js'),
74
- responseRetry: require('./interceptor/response-retry.js'),
75
- signal: require('./interceptor/signal.js'),
76
- proxy: require('./interceptor/proxy.js'),
77
- cache: require('./interceptor/cache.js'),
78
- }
79
-
80
- async function request(url, opts) {
81
- if (typeof url === 'string') {
82
- url = new URL(url)
83
- } else if (url instanceof URL) {
84
- // Do nothing...
85
- } else if (typeof url.origin === 'string' && typeof (url.path ?? url.pathname) === 'string') {
86
- // Do nothing...
87
- } else {
88
- throw new Error('missing url')
89
- }
90
-
91
- if (opts == null && typeof url === 'object' && url != null) {
92
- opts = url
93
- }
94
-
95
- const method = opts.method ?? (opts.body ? 'POST' : 'GET')
96
- const idempotent = opts.idempotent ?? (method === 'GET' || method === 'HEAD')
97
-
98
- let headers
99
- if (Array.isArray(opts.headers)) {
100
- headers = parseHeaders(opts.headers)
101
- } else {
102
- headers = opts.headers
103
- }
104
-
105
- if (method === 'CONNECT') {
106
- throw new createError.MethodNotAllowed()
107
- }
108
-
109
- if (
110
- (method === 'HEAD' || method === 'GET') &&
111
- (parseInt(headers['content-length']) > 0 || headers['transfer-encoding'])
112
- ) {
113
- throw new createError.BadRequest('HEAD and GET cannot have body')
114
- }
115
-
116
- opts = {
117
- url,
118
- method,
119
- body: opts.body,
120
- headers: {
121
- 'request-id': xuid(),
122
- 'user-agent': opts.userAgent ?? globalThis.userAgent,
123
- ...headers,
124
- },
125
- origin: url.origin,
126
- path: url.path ? url.path : url.search ? `${url.pathname}${url.search ?? ''}` : url.pathname,
127
- reset: opts.reset ?? false,
128
- headersTimeout: opts.headersTimeout,
129
- bodyTimeout: opts.bodyTimeout,
130
- idempotent,
131
- signal: opts.signal,
132
- retry: opts.retry ?? 8,
133
- proxy: opts.proxy,
134
- cache: opts.cache,
135
- upgrade: opts.upgrade,
136
- follow: { count: opts.maxRedirections ?? 8, ...opts.redirect, ...opts.follow },
137
- logger: opts.logger,
138
- maxRedirections: 0, // Disable undici's redirect handling.
139
- }
140
-
141
- const expectsPayload = opts.method === 'PUT' || opts.method === 'POST' || opts.method === 'PATCH'
142
-
143
- if (opts.headers['content-length'] === '0' && !expectsPayload) {
144
- // https://tools.ietf.org/html/rfc7230#section-3.3.2
145
- // A user agent SHOULD NOT send a Content-Length header field when
146
- // the request message does not contain a payload body and the method
147
- // semantics do not anticipate such a body.
148
-
149
- // undici will error if provided an unexpected content-length: 0 header.
150
- delete opts.headers['content-length']
151
- }
152
-
153
- const dispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
154
-
155
- return new Promise((resolve) => {
156
- let dispatch = (opts, handler) => dispatcher.dispatch(opts, handler)
157
-
158
- dispatch = dispatchers.catch(dispatch)
159
- dispatch = dispatchers.abort(dispatch)
160
- dispatch = dispatchers.log(dispatch)
161
- dispatch = opts.upgrade ? dispatch : dispatchers.responseRetry(dispatch)
162
- dispatch = opts.upgrade ? dispatch : dispatchers.responseStatusRetry(dispatch)
163
- dispatch = opts.upgrade ? dispatch : dispatchers.responseBodyRetry(dispatch)
164
- dispatch = opts.upgrade ? dispatch : dispatchers.content(dispatch)
165
- dispatch = dispatchers.redirect(dispatch)
166
- dispatch = dispatchers.signal(dispatch)
167
- dispatch = opts.upgrade ? dispatch : dispatchers.cache(dispatch)
168
- dispatch = dispatchers.proxy(dispatch)
169
-
170
- dispatch(opts, {
171
- resolve,
172
- logger: opts.logger,
173
- /** @type {Function | null} */ abort: null,
174
- /** @type {stream.Readable | null} */ body: null,
175
- onConnect(abort) {
176
- this.abort = abort
177
- },
178
- onUpgrade(statusCode, rawHeaders, socket) {
179
- const headers = parseHeaders(rawHeaders)
180
-
181
- if (statusCode !== 101) {
182
- this.abort(createError(statusCode, { headers }))
183
- } else {
184
- this.resolve({ headers, socket })
185
- }
186
- },
187
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
188
- assert(this.abort)
189
-
190
- const headers = parseHeaders(rawHeaders)
191
-
192
- if (statusCode >= 400) {
193
- this.abort(createError(statusCode, { headers }))
194
- } else {
195
- assert(statusCode >= 200)
196
-
197
- const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
198
-
199
- this.body = new Readable({
200
- read: resume,
201
- highWaterMark: 128 * 1024,
202
- statusCode,
203
- statusMessage,
204
- headers,
205
- size: Number.isFinite(contentLength) ? contentLength : null,
206
- }).on('error', (err) => {
207
- if (this.logger && this.body?.listenerCount('error') === 1) {
208
- this.logger.error({ err }, 'unhandled response body error')
209
- }
210
- })
211
-
212
- this.resolve(this.body)
213
- this.resolve = null
214
- }
215
-
216
- return false
217
- },
218
- onData(chunk) {
219
- assert(this.body)
220
- return this.body.push(chunk)
221
- },
222
- onComplete() {
223
- assert(this.body)
224
- this.body.push(null)
225
- },
226
- onError(err) {
227
- if (this.body) {
228
- this.body.destroy(err)
229
- } else {
230
- this.resolve(Promise.reject(err))
231
- }
232
- },
233
- })
234
- })
235
- }
236
-
237
- module.exports = { request }
@@ -1,65 +0,0 @@
1
- const { AbortError } = require('../../../errors')
2
-
3
- class Handler {
4
- constructor(opts, { handler }) {
5
- this.handler = handler
6
- this.pos = 0
7
- this.reason = null
8
- }
9
-
10
- onConnect(abort) {
11
- this.abort = abort
12
- this.handler.onConnect?.((reason) => {
13
- this.reason = reason ?? new AbortError()
14
- })
15
- }
16
-
17
- onBodySent(chunk) {
18
- return this.handler.onBodySent?.(chunk)
19
- }
20
-
21
- onUpgrade(statusCode, rawHeaders, socket) {
22
- return this.handler.onUpgrade?.(statusCode, rawHeaders, socket)
23
- }
24
-
25
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
26
- if (this.reason == null) {
27
- const ret = this.handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
28
- if (this.reason == null) {
29
- return ret
30
- }
31
- }
32
-
33
- return true
34
- }
35
-
36
- onData(chunk) {
37
- if (this.reason == null) {
38
- const ret = this.handler.onData?.(chunk)
39
- if (this.reason == null) {
40
- return ret
41
- }
42
- }
43
-
44
- this.pos += chunk.length
45
- if (this.pos < 128 * 1024) {
46
- return true
47
- }
48
-
49
- this.abort(this.reason)
50
-
51
- return false
52
- }
53
-
54
- onComplete(rawTrailers) {
55
- return this.reason == null
56
- ? this.handler.onComplete?.(rawTrailers)
57
- : this.handler.onError?.(this.reason)
58
- }
59
-
60
- onError(err) {
61
- return this.handler.onError?.(err)
62
- }
63
- }
64
-
65
- module.exports = (dispatch) => (opts, handler) => dispatch(opts, new Handler(opts, { handler }))
@@ -1,202 +0,0 @@
1
- const assert = require('node:assert')
2
- const { LRUCache } = require('lru-cache')
3
- const cacheControlParser = require('cache-control-parser')
4
-
5
- class CacheHandler {
6
- constructor({ key, handler, store }) {
7
- this.key = key
8
- this.handler = handler
9
- this.store = store
10
- this.value = null
11
- }
12
-
13
- onConnect(abort) {
14
- return this.handler.onConnect(abort)
15
- }
16
-
17
- onUpgrade(statusCode, rawHeaders, socket) {
18
- return this.handler.onUpgrade?.(statusCode, rawHeaders, socket)
19
- }
20
-
21
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
22
- // NOTE: Only cache 307 respones for now...
23
- if (statusCode !== 307) {
24
- return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
25
- }
26
-
27
- let cacheControl
28
- for (let n = 0; n < rawHeaders.length; n += 2) {
29
- if (
30
- rawHeaders[n].length === 'cache-control'.length &&
31
- rawHeaders[n].toString().toLowerCase() === 'cache-control'
32
- ) {
33
- cacheControl = cacheControlParser.parse(rawHeaders[n + 1].toString())
34
- break
35
- }
36
- }
37
-
38
- if (
39
- cacheControl &&
40
- cacheControl.public &&
41
- !cacheControl.private &&
42
- !cacheControl['no-store'] &&
43
- // TODO (fix): Support all cache control directives...
44
- // !opts.headers['no-transform'] &&
45
- !cacheControl['no-cache'] &&
46
- !cacheControl['must-understand'] &&
47
- !cacheControl['must-revalidate'] &&
48
- !cacheControl['proxy-revalidate']
49
- ) {
50
- const maxAge = cacheControl['s-max-age'] ?? cacheControl['max-age']
51
- const ttl = cacheControl.immutable
52
- ? 31556952 // 1 year
53
- : Number(maxAge)
54
-
55
- if (ttl > 0) {
56
- this.value = {
57
- statusCode,
58
- statusMessage,
59
- rawHeaders,
60
- rawTrailers: null,
61
- body: [],
62
- size: 0,
63
- ttl: ttl * 1e3,
64
- }
65
- }
66
- }
67
-
68
- return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
69
- }
70
-
71
- onData(chunk) {
72
- if (this.value) {
73
- this.value.size += chunk.bodyLength
74
- if (this.value.size > this.store.maxEntrySize) {
75
- this.value = null
76
- } else {
77
- this.value.body.push(chunk)
78
- }
79
- }
80
- return this.handler.onData(chunk)
81
- }
82
-
83
- onComplete(rawTrailers) {
84
- if (this.value) {
85
- this.value.rawTrailers = rawTrailers
86
- this.store.set(this.key, this.value, this.value.ttl)
87
- }
88
- return this.handler.onComplete(rawTrailers)
89
- }
90
-
91
- onError(err) {
92
- return this.handler.onError(err)
93
- }
94
- }
95
-
96
- // TODO (fix): Async filesystem cache.
97
- class CacheStore {
98
- constructor({ maxSize, maxEntrySize }) {
99
- this.maxSize = maxSize
100
- this.maxEntrySize = maxEntrySize
101
- this.cache = new LRUCache({
102
- maxSize,
103
- sizeCalculation: (value) => value.body.byteLength,
104
- })
105
- }
106
-
107
- set(key, value, ttl) {
108
- this.cache.set(key, value, ttl)
109
- }
110
-
111
- get(key) {
112
- return this.cache.get(key)
113
- }
114
- }
115
-
116
- function makeKey(opts) {
117
- // NOTE: Ignores headers...
118
- return `${opts.method}:${opts.path}`
119
- }
120
-
121
- const DEFAULT_CACHE_STORE = new CacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })
122
-
123
- module.exports = (dispatch) => (opts, handler) => {
124
- if (!opts.cache) {
125
- return dispatch(opts, handler)
126
- }
127
-
128
- if (opts.method !== 'GET' && opts.method !== 'HEAD') {
129
- dispatch(opts, handler)
130
- return
131
- }
132
-
133
- if (opts.headers?.['cache-control'] || opts.headers?.authorization) {
134
- // TODO (fix): Support all cache control directives...
135
- // const cacheControl = cacheControlParser.parse(opts.headers['cache-control'])
136
- // cacheControl['no-cache']
137
- // cacheControl['no-store']
138
- // cacheControl['max-age']
139
- // cacheControl['max-stale']
140
- // cacheControl['min-fresh']
141
- // cacheControl['no-transform']
142
- // cacheControl['only-if-cached']
143
- dispatch(opts, handler)
144
- return
145
- }
146
-
147
- // TODO (fix): Support body...
148
- assert(opts.method === 'GET' || opts.method === 'HEAD')
149
-
150
- // Dump body...
151
- opts.body?.on('error', () => {}).resume()
152
-
153
- const store = opts.cache === true ? DEFAULT_CACHE_STORE : opts.cache
154
-
155
- if (!store) {
156
- throw new Error(`Cache store not provided.`)
157
- }
158
-
159
- let key = makeKey(opts)
160
- let value = store.get(key)
161
-
162
- if (value == null && opts.method === 'HEAD') {
163
- key = makeKey({ ...opts, method: 'GET' })
164
- value = store.get(key)
165
- }
166
-
167
- if (value) {
168
- const { statusCode, statusMessage, rawHeaders, rawTrailers, body } = value
169
- const ac = new AbortController()
170
- const signal = ac.signal
171
-
172
- const resume = () => {}
173
- const abort = () => {
174
- ac.abort()
175
- }
176
-
177
- try {
178
- handler.onConnect(abort)
179
- signal.throwIfAborted()
180
- handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
181
- signal.throwIfAborted()
182
- if (opts.method !== 'HEAD') {
183
- for (const chunk of body) {
184
- const ret = handler.onData(chunk)
185
- signal.throwIfAborted()
186
- if (ret === false) {
187
- // TODO (fix): back pressure...
188
- }
189
- }
190
- handler.onComplete(rawTrailers)
191
- signal.throwIfAborted()
192
- } else {
193
- handler.onComplete([])
194
- signal.throwIfAborted()
195
- }
196
- } catch (err) {
197
- handler.onError(err)
198
- }
199
- } else {
200
- dispatch(opts, new CacheHandler({ handler, store, key: makeKey(opts) }))
201
- }
202
- }
@@ -1,63 +0,0 @@
1
- class Handler {
2
- constructor(opts, { handler }) {
3
- this.handler = handler
4
- }
5
-
6
- onConnect(abort) {
7
- this.abort = abort
8
- try {
9
- return this.handler.onConnect?.(abort)
10
- } catch (err) {
11
- this.abort(err)
12
- }
13
- }
14
-
15
- onUpgrade(statusCode, rawHeaders, socket) {
16
- try {
17
- return this.handler.onUpgrade?.(statusCode, rawHeaders, socket)
18
- } catch (err) {
19
- this.abort(err)
20
- }
21
- }
22
-
23
- onBodySent(chunk) {
24
- try {
25
- return this.handler.onBodySent?.(chunk)
26
- } catch (err) {
27
- this.abort(err)
28
- }
29
- }
30
-
31
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
32
- try {
33
- return this.handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
34
- } catch (err) {
35
- this.abort(err)
36
- return false
37
- }
38
- }
39
-
40
- onData(chunk) {
41
- try {
42
- return this.handler.onData?.(chunk)
43
- } catch (err) {
44
- this.abort(err)
45
- return false
46
- }
47
- }
48
-
49
- onComplete(rawTrailers) {
50
- try {
51
- return this.handler.onComplete?.(rawTrailers)
52
- } catch (err) {
53
- this.abort(err)
54
- return false
55
- }
56
- }
57
-
58
- onError(err) {
59
- return this.handler.onError?.(err)
60
- }
61
- }
62
-
63
- module.exports = (dispatch) => (opts, handler) => dispatch(opts, new Handler(opts, { handler }))