@nxtedition/lib 14.3.0 → 15.0.0

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
@@ -98,13 +98,14 @@ module.exports = function (appConfig, onTerminate) {
98
98
  // process.env.name is the pm2 name of the process
99
99
  appConfig.instanceId ?? appConfig.containerId ?? process.env.name ?? os.hostname()
100
100
 
101
- const userAgent =
101
+ const userAgent = globalThis.userAgent = (
102
102
  appConfig.userAgent ??
103
103
  (serviceName &&
104
104
  `${serviceName}/${
105
105
  serviceVersion || '*'
106
106
  } (module:${serviceModule}; instance:${serviceInstanceId}) Node/${process.version}`) ??
107
107
  null
108
+ )
108
109
 
109
110
  const terminate = async (finalLogger) => {
110
111
  finalLogger ??= logger
@@ -147,7 +148,7 @@ module.exports = function (appConfig, onTerminate) {
147
148
  module: serviceModule,
148
149
  base: loggerConfig?.base ? { ...loggerConfig.base } : {},
149
150
  },
150
- terminate
151
+ terminate,
151
152
  )
152
153
  }
153
154
 
@@ -228,7 +229,7 @@ module.exports = function (appConfig, onTerminate) {
228
229
  } else {
229
230
  return {}
230
231
  }
231
- })
232
+ }),
232
233
  )
233
234
 
234
235
  if (couchConfig.url) {
@@ -256,7 +257,7 @@ module.exports = function (appConfig, onTerminate) {
256
257
  } else {
257
258
  return {}
258
259
  }
259
- })
260
+ }),
260
261
  )
261
262
 
262
263
  if (!dsConfig.credentials || !dsConfig.url) {
@@ -310,7 +311,7 @@ module.exports = function (appConfig, onTerminate) {
310
311
 
311
312
  logger[level](
312
313
  { ds: { connectionState, username: userName, url: dsConfig.url } },
313
- 'Deepstream Connection State Changed.'
314
+ 'Deepstream Connection State Changed.',
314
315
  )
315
316
 
316
317
  prevConnectionState = connectionState
@@ -351,7 +352,7 @@ module.exports = function (appConfig, onTerminate) {
351
352
  rx.exhaustMap(() => {
352
353
  const ret = appConfig.stats({ ds, couch, logger })
353
354
  return ret?.then || ret?.subscribe ? ret : rxjs.of(ret)
354
- })
355
+ }),
355
356
  )
356
357
  } else if (typeof appConfig.stats === 'object') {
357
358
  stats$ = rxjs.timer(0, 10e3).pipe(rx.map(() => appConfig.stats))
@@ -369,12 +370,12 @@ module.exports = function (appConfig, onTerminate) {
369
370
  rx.retryWhen((err$) =>
370
371
  err$.pipe(
371
372
  rx.tap((err) => logger.error({ err }, 'monitor.stats')),
372
- rx.delay(10e3)
373
- )
373
+ rx.delay(10e3),
374
+ ),
374
375
  ),
375
376
  rx.startWith({}),
376
377
  rx.publishReplay(1),
377
- rx.refCount()
378
+ rx.refCount(),
378
379
  )
379
380
 
380
381
  monitorProviders.stats$ = stats$
@@ -393,7 +394,7 @@ module.exports = function (appConfig, onTerminate) {
393
394
  heap: v8.getHeapStatistics(),
394
395
  ...stats,
395
396
  },
396
- 'STATS'
397
+ 'STATS',
397
398
  )
398
399
  elu1 = elu2
399
400
  }
@@ -422,7 +423,7 @@ module.exports = function (appConfig, onTerminate) {
422
423
  })
423
424
  .pipe(
424
425
  rx.catchError((err) => rxjs.of({ warnings: [err.message] })),
425
- rx.repeatWhen(() => rxjs.timer(10e3))
426
+ rx.repeatWhen(() => rxjs.timer(10e3)),
426
427
  )
427
428
  } else if (appConfig.status && typeof appConfig.status === 'object') {
428
429
  status$ = rxjs.timer(0, 10e3).pipe(rx.exhaustMap(() => appConfig.status))
@@ -448,7 +449,7 @@ module.exports = function (appConfig, onTerminate) {
448
449
  }),
449
450
  rx.startWith([]),
450
451
  rx.distinctUntilChanged(fp.isEqual),
451
- rx.repeatWhen((complete$) => complete$.pipe(rx.delay(10e3)))
452
+ rx.repeatWhen((complete$) => complete$.pipe(rx.delay(10e3))),
452
453
  ),
453
454
  toobusy
454
455
  ? rxjs.timer(0, 1e3).pipe(
@@ -462,10 +463,10 @@ module.exports = function (appConfig, onTerminate) {
462
463
  msg: `lag: ${toobusy.lag()}`,
463
464
  },
464
465
  ]
465
- : []
466
+ : [],
466
467
  ),
467
468
  rx.startWith([]),
468
- rx.distinctUntilChanged(fp.isEqual)
469
+ rx.distinctUntilChanged(fp.isEqual),
469
470
  )
470
471
  : rxjs.of({}),
471
472
  couch
@@ -485,7 +486,7 @@ module.exports = function (appConfig, onTerminate) {
485
486
  }
486
487
  }),
487
488
  rx.startWith([]),
488
- rx.distinctUntilChanged(fp.isEqual)
489
+ rx.distinctUntilChanged(fp.isEqual),
489
490
  )
490
491
  : rxjs.of({}),
491
492
  ds
@@ -562,7 +563,7 @@ module.exports = function (appConfig, onTerminate) {
562
563
  }
563
564
 
564
565
  return messages
565
- })
566
+ }),
566
567
  )
567
568
  .subscribe(o)
568
569
 
@@ -573,7 +574,7 @@ module.exports = function (appConfig, onTerminate) {
573
574
  }).pipe(rx.startWith([]), rx.distinctUntilChanged(fp.isEqual))
574
575
  : rxjs.of({}),
575
576
  rxjs.timer(0, 10e3),
576
- ].filter(Boolean)
577
+ ].filter(Boolean),
577
578
  )
578
579
  .pipe(
579
580
  rx.auditTime(1e3),
@@ -597,7 +598,7 @@ module.exports = function (appConfig, onTerminate) {
597
598
  ...message,
598
599
  message: undefined,
599
600
  msg: message.message,
600
- }
601
+ },
601
602
  )
602
603
  .map((message) =>
603
604
  message.id
@@ -605,9 +606,9 @@ module.exports = function (appConfig, onTerminate) {
605
606
  : {
606
607
  ...message,
607
608
  id: hashString(
608
- [message.msg, message].find(fp.isString) ?? JSON.stringify(message)
609
+ [message.msg, message].find(fp.isString) ?? JSON.stringify(message),
609
610
  ),
610
- }
611
+ },
611
612
  )
612
613
 
613
614
  return { ...status, messages, timestamp: Date.now() }
@@ -622,7 +623,7 @@ module.exports = function (appConfig, onTerminate) {
622
623
  rx.startWith({}),
623
624
  rx.distinctUntilChanged(fp.isEqual),
624
625
  rx.publishReplay(1),
625
- rx.refCount()
626
+ rx.refCount(),
626
627
  )
627
628
 
628
629
  const loggerSubscription = status$
@@ -747,13 +748,13 @@ module.exports = function (appConfig, onTerminate) {
747
748
  },
748
749
  ]
749
750
  .flat()
750
- .filter(Boolean)
751
+ .filter(Boolean),
751
752
  )
752
753
 
753
754
  server = createServer(
754
755
  typeof appConfig.http === 'object' ? appConfig.http : {},
755
756
  { ds, couch, config: httpConfig, logger },
756
- middleware
757
+ middleware,
757
758
  )
758
759
 
759
760
  if (httpConfig.keepAlive != null) {
package/errors.js CHANGED
@@ -3,8 +3,8 @@ const fp = require('lodash/fp.js')
3
3
  const { toString } = Object.prototype
4
4
 
5
5
  module.exports.AbortError = class AbortError extends Error {
6
- constructor() {
7
- super('The operation was aborted')
6
+ constructor(message) {
7
+ super(message ?? 'The operation was aborted')
8
8
  this.code = 'ABORT_ERR'
9
9
  this.name = 'AbortError'
10
10
  }
@@ -34,7 +34,7 @@ module.exports.parseError = function parseError(error) {
34
34
  {
35
35
  ...properties,
36
36
  cause: cause ? parseError(error.cause) : undefined,
37
- }
37
+ },
38
38
  )
39
39
  }
40
40
 
@@ -101,7 +101,7 @@ module.exports.serializeError = function serializeError(error) {
101
101
  data,
102
102
  cause,
103
103
  errors,
104
- })
104
+ }),
105
105
  )
106
106
  }
107
107
 
@@ -115,7 +115,7 @@ module.exports.makeMessages = function makeMessages(error, options) {
115
115
  return fp.pipe(
116
116
  fp.flattenDeep,
117
117
  fp.flatMap((x) => makeMessages(x, null)),
118
- fp.uniqBy('id')
118
+ fp.uniqBy('id'),
119
119
  )(error)
120
120
  } else if (Array.isArray(error.messages)) {
121
121
  return makeMessages(error.messages, null)
@@ -127,11 +127,11 @@ module.exports.makeMessages = function makeMessages(error, options) {
127
127
  const level = parseInt(error.level) || options?.level || 50
128
128
  const code =
129
129
  [error?.code, options?.codes?.[error?.code]].find(
130
- (x) => typeof x === 'string' && x.length > 0
130
+ (x) => typeof x === 'string' && x.length > 0,
131
131
  ) ?? undefined
132
132
  const msg =
133
133
  [error.msg, error.message, code?.toLowerCase().replace('_', ' ')].find(
134
- (x) => typeof x === 'string' && x.length > 0
134
+ (x) => typeof x === 'string' && x.length > 0,
135
135
  ) || 'unknown error'
136
136
 
137
137
  let data = error.data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "14.3.0",
3
+ "version": "15.0.0",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "files": [
@@ -8,6 +8,7 @@
8
8
  "ass.js",
9
9
  "rxjs/*",
10
10
  "util/*",
11
+ "undici/*",
11
12
  "subtract-ranges.js",
12
13
  "serializers.js",
13
14
  "elasticsearch.js",
@@ -22,9 +23,7 @@
22
23
  "app.js",
23
24
  "errors.js",
24
25
  "worker.js",
25
- "proxy.js",
26
26
  "stream.js",
27
- "undici.js",
28
27
  "timeline.js",
29
28
  "docker-secrets.js"
30
29
  ],
@@ -71,6 +70,7 @@
71
70
  "/__tests__"
72
71
  ],
73
72
  "dependencies": {
73
+ "cache-control-parser": "^2.0.4",
74
74
  "date-fns": "^2.29.3",
75
75
  "fast-querystring": "^1.1.1",
76
76
  "hasha": "^5.2.2",
@@ -78,6 +78,7 @@
78
78
  "json5": "^2.2.3",
79
79
  "koa-compose": "^4.1.0",
80
80
  "lodash": "^4.17.21",
81
+ "lru-cache": "^10.0.1",
81
82
  "mime": "^3.0.0",
82
83
  "moment-timezone": "^0.5.43",
83
84
  "nconf": "^0.12.0",
@@ -90,23 +91,25 @@
90
91
  "smpte-timecode": "^1.3.3",
91
92
  "split-string": "^6.0.0",
92
93
  "toobusy-js": "^0.5.1",
93
- "undici": "^5.22.0",
94
+ "undici": "^5.25.2",
94
95
  "url-join": "^4.0.0",
95
96
  "xuid": "^4.1.2"
96
97
  },
97
98
  "devDependencies": {
98
- "eslint": "^8.38.0",
99
- "eslint-config-prettier": "^8.8.0",
99
+ "@types/node": "^20.6.3",
100
+ "eslint": "^8.50.0",
101
+ "eslint-config-prettier": "^9.0.0",
100
102
  "eslint-config-standard": "^17.0.0",
101
- "eslint-plugin-import": "^2.27.5",
102
- "eslint-plugin-n": "^15.7.0",
103
+ "eslint-plugin-import": "^2.28.1",
104
+ "eslint-plugin-n": "^16.1.0",
103
105
  "eslint-plugin-node": "^11.1.0",
104
106
  "eslint-plugin-promise": "^6.0.0",
105
107
  "husky": "^8.0.3",
106
- "lint-staged": "^13.2.1",
108
+ "lint-staged": "^14.0.1",
107
109
  "pinst": "^3.0.0",
108
- "prettier": "^2.8.7",
110
+ "prettier": "^3.0.3",
109
111
  "rxjs": "^7.5.6",
112
+ "send": "^0.18.0",
110
113
  "tap": "^16.3.4"
111
114
  },
112
115
  "peerDependencies": {
@@ -0,0 +1,192 @@
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, ...opts }) {
10
+ super(opts)
11
+ this.statusCode = statusCode
12
+ this.statusMessage = statusMessage
13
+ this.headers = headers
14
+ this.body = this
15
+ }
16
+
17
+ async text() {
18
+ const dec = new TextDecoder()
19
+ let str = ''
20
+ for await (const chunk of this) {
21
+ if (typeof chunk === 'string') {
22
+ str += chunk
23
+ } else {
24
+ str += dec.decode(chunk, { stream: true })
25
+ }
26
+ }
27
+ // Flush the streaming TextDecoder so that any pending
28
+ // incomplete multibyte characters are handled.
29
+ str += dec.decode(undefined, { stream: false })
30
+ return str
31
+ }
32
+
33
+ async json() {
34
+ return JSON.parse(await this.text())
35
+ }
36
+
37
+ async arrayBuffer() {
38
+ const buffers = []
39
+ for await (const chunk of this) {
40
+ buffers.push(chunk)
41
+ }
42
+ return Buffer.concat(buffers)
43
+ }
44
+
45
+ async buffer() {
46
+ return Buffer.from(await this.arrayBuffer())
47
+ }
48
+
49
+ async dump() {
50
+ let n = 0
51
+ try {
52
+ for await (const chunk of this) {
53
+ // do nothing
54
+ n += chunk.length
55
+ if (n > 128 * 1024) {
56
+ break
57
+ }
58
+ }
59
+ } catch {
60
+ this.destroy()
61
+ }
62
+ }
63
+ }
64
+
65
+ const dispatchers = {
66
+ abort: require('./interceptor/abort.js'),
67
+ catch: require('./interceptor/catch.js'),
68
+ content: require('./interceptor/content.js'),
69
+ responseBodyDump: require('./interceptor/response-body-dump.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
+ }
78
+
79
+ async function request(urlOrOpts, opts = {}) {
80
+ let url
81
+ if (typeof urlOrOpts === 'string') {
82
+ url = new URL(urlOrOpts)
83
+ } else if (urlOrOpts instanceof URL) {
84
+ url = urlOrOpts
85
+ } else if (typeof urlOrOpts?.origin === 'string' && typeof urlOrOpts?.path === 'string') {
86
+ url = urlOrOpts
87
+ } else if (typeof urlOrOpts === 'object' && urlOrOpts != null) {
88
+ opts = urlOrOpts
89
+ url = opts.url
90
+ }
91
+
92
+ const method = opts.method ?? (opts.body ? 'POST' : 'GET')
93
+ const idempotent = opts.idempotent ?? (method === 'GET' || method === 'HEAD')
94
+ const dump = opts.dump ?? method === 'HEAD'
95
+
96
+ let headers
97
+ if (Array.isArray(opts.headers)) {
98
+ headers = parseHeaders(opts.headers)
99
+ } else {
100
+ headers = opts.headers
101
+ }
102
+
103
+ opts = {
104
+ url,
105
+ method,
106
+ body: opts.body,
107
+ headers: {
108
+ 'request-id': xuid(),
109
+ 'user-agent': opts.userAgent ?? globalThis.userAgent,
110
+ ...headers,
111
+ },
112
+ origin: opts.origin ?? url.origin,
113
+ path: opts.path ?? url.search ? `${url.pathname}${url.search ?? ''}` : url.pathname,
114
+ reset: opts.reset ?? false,
115
+ headersTimeout: opts.headersTimeout,
116
+ bodyTimeout: opts.bodyTimeout,
117
+ idempotent,
118
+ signal: opts.signal,
119
+ retry: opts.retry ?? 8,
120
+ follow: { count: opts.maxRedirections ?? 8, ...opts.redirect, ...opts.follow },
121
+ dump,
122
+ logger: opts.logger,
123
+ }
124
+
125
+ const dispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
126
+
127
+ return new Promise((resolve) => {
128
+ let dispatch = (opts, handler) => dispatcher.dispatch(opts, handler)
129
+
130
+ dispatch = dispatchers.catch(dispatch)
131
+ dispatch = dispatchers.abort(dispatch)
132
+ dispatch = dispatchers.log(dispatch)
133
+ dispatch = dispatchers.responseRetry(dispatch)
134
+ dispatch = dispatchers.responseStatusRetry(dispatch)
135
+ dispatch = dispatchers.responseBodyRetry(dispatch)
136
+ dispatch = dispatchers.content(dispatch)
137
+ dispatch = dispatchers.responseBodyDump(dispatch)
138
+ dispatch = dispatchers.redirect(dispatch)
139
+ dispatch = dispatchers.signal(dispatch)
140
+ dispatch = dispatchers.proxy(dispatch)
141
+
142
+ dispatch(opts, {
143
+ resolve,
144
+ /** @type {Function | null} */ abort: null,
145
+ /** @type {stream.Readable | null} */ body: null,
146
+ onConnect(abort) {
147
+ this.abort = abort
148
+ },
149
+ onHeaders(statusCode, rawHeaders, resume, statusMessage) {
150
+ assert(this.abort)
151
+
152
+ const headers = parseHeaders(rawHeaders)
153
+
154
+ if (statusCode >= 400) {
155
+ this.abort(createError(statusCode, { headers }))
156
+ } else {
157
+ assert(statusCode >= 200)
158
+
159
+ this.body = new Readable({
160
+ read: resume,
161
+ highWaterMark: 128 * 1024,
162
+ statusCode,
163
+ statusMessage,
164
+ headers,
165
+ })
166
+
167
+ this.resolve(this.body)
168
+ this.resolve = null
169
+ }
170
+
171
+ return false
172
+ },
173
+ onData(chunk) {
174
+ assert(this.body)
175
+ return this.body.push(chunk)
176
+ },
177
+ onComplete() {
178
+ assert(this.body)
179
+ this.body.push(null)
180
+ },
181
+ onError(err) {
182
+ if (this.body) {
183
+ this.body.destroy(err)
184
+ } else {
185
+ this.resolve(Promise.reject(err))
186
+ }
187
+ },
188
+ })
189
+ })
190
+ }
191
+
192
+ module.exports = { request }
@@ -0,0 +1,61 @@
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
+ onHeaders(statusCode, rawHeaders, resume, statusMessage) {
22
+ if (this.reason == null) {
23
+ const ret = this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
24
+ if (this.reason == null) {
25
+ return ret
26
+ }
27
+ }
28
+
29
+ return true
30
+ }
31
+
32
+ onData(chunk) {
33
+ if (this.reason == null) {
34
+ const ret = this.handler.onData(chunk)
35
+ if (this.reason == null) {
36
+ return ret
37
+ }
38
+ }
39
+
40
+ this.pos += chunk.length
41
+ if (this.pos < 128 * 1024) {
42
+ return true
43
+ }
44
+
45
+ this.abort(this.reason)
46
+
47
+ return false
48
+ }
49
+
50
+ onComplete(rawTrailers) {
51
+ return this.reason == null
52
+ ? this.handler.onComplete(rawTrailers)
53
+ : this.handler.onError(this.reason)
54
+ }
55
+
56
+ onError(err) {
57
+ return this.handler.onError(err)
58
+ }
59
+ }
60
+
61
+ module.exports = (dispatch) => (opts, handler) => dispatch(opts, new Handler(opts, { handler }))
@@ -0,0 +1,55 @@
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
+ onBodySent(chunk) {
16
+ try {
17
+ return this.handler.onBodySent(chunk)
18
+ } catch (err) {
19
+ this.abort(err)
20
+ }
21
+ }
22
+
23
+ onHeaders(statusCode, rawHeaders, resume, statusMessage) {
24
+ try {
25
+ return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
26
+ } catch (err) {
27
+ this.abort(err)
28
+ return false
29
+ }
30
+ }
31
+
32
+ onData(chunk) {
33
+ try {
34
+ return this.handler.onData(chunk)
35
+ } catch (err) {
36
+ this.abort(err)
37
+ return false
38
+ }
39
+ }
40
+
41
+ onComplete(rawTrailers) {
42
+ try {
43
+ return this.handler.onComplete(rawTrailers)
44
+ } catch (err) {
45
+ this.abort(err)
46
+ return false
47
+ }
48
+ }
49
+
50
+ onError(err) {
51
+ return this.handler.onError(err)
52
+ }
53
+ }
54
+
55
+ module.exports = (dispatch) => (opts, handler) => dispatch(opts, new Handler(opts, { handler }))