@nxtedition/lib 26.4.4 → 26.4.6

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
@@ -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
- // process.env.name is the pm2 name of the process
133
- appConfig.instanceId ?? appConfig.containerId ?? process.env.name ?? os.hostname()
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}) worker:${serviceWorkerId} Node/${process.version}`) ??
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.4.4",
3
+ "version": "26.4.6",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
package/rxjs/auditMap.js CHANGED
@@ -5,6 +5,7 @@ function auditMapImpl(project) {
5
5
  let pendingValue = null
6
6
  let hasPendingValue = false
7
7
  let isComplete = false
8
+ let abortController = null
8
9
 
9
10
  let innerSubscription = null
10
11
  let outerSubscription = null
@@ -15,6 +16,7 @@ function auditMapImpl(project) {
15
16
 
16
17
  function _innerComplete() {
17
18
  innerSubscription = null
19
+ abortController = null
18
20
 
19
21
  if (hasPendingValue) {
20
22
  const value = pendingValue
@@ -30,9 +32,30 @@ function auditMapImpl(project) {
30
32
  o.next(val)
31
33
  }
32
34
 
35
+ function _drain() {
36
+ if (!hasPendingValue) {
37
+ return
38
+ }
39
+ const value = pendingValue
40
+ pendingValue = null
41
+ hasPendingValue = false
42
+ return { value }
43
+ }
44
+
33
45
  function _tryNext(value) {
34
46
  try {
35
- const result = project(value)
47
+ const result = project(value, {
48
+ get signal() {
49
+ if (!abortController) {
50
+ abortController = new AbortController()
51
+ if (hasPendingValue) {
52
+ abortController.abort()
53
+ }
54
+ }
55
+ return abortController.signal
56
+ },
57
+ _drain,
58
+ })
36
59
  const observable = typeof result.then === 'function' ? from(result) : result
37
60
  innerSubscription = observable.subscribe({
38
61
  next: _innerNext,
@@ -51,6 +74,7 @@ function auditMapImpl(project) {
51
74
  if (innerSubscription) {
52
75
  pendingValue = value
53
76
  hasPendingValue = true
77
+ abortController?.abort()
54
78
  } else {
55
79
  _tryNext(value)
56
80
  }
@@ -60,6 +84,8 @@ function auditMapImpl(project) {
60
84
  isComplete = true
61
85
  if (!innerSubscription) {
62
86
  o.complete()
87
+ } else {
88
+ abortController?.abort()
63
89
  }
64
90
  }
65
91
 
@@ -0,0 +1,64 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import auditMap from './auditMap.js'
4
+ import * as rxjs from 'rxjs'
5
+ import tp from 'node:timers/promises'
6
+
7
+ test('auditMap sync', (t) => {
8
+ t.plan(1, { wait: true })
9
+ rxjs
10
+ .of(1, 2, 3)
11
+ .pipe(
12
+ auditMap((val) => rxjs.of(val * 2)),
13
+ rxjs.toArray(),
14
+ )
15
+ .subscribe((val) => {
16
+ t.assert.deepStrictEqual(val, [2, 4, 6])
17
+ })
18
+ })
19
+
20
+ test('auditMap async', (t) => {
21
+ t.plan(1, { wait: true })
22
+ rxjs
23
+ .of(1, 2, 3)
24
+ .pipe(
25
+ auditMap(async (val) => val * 2),
26
+ rxjs.toArray(),
27
+ )
28
+ .subscribe((val) => {
29
+ t.assert.deepStrictEqual(val, [2, 6])
30
+ })
31
+ })
32
+
33
+ test('auditMap drain', (t) => {
34
+ t.plan(1, { wait: true })
35
+ rxjs
36
+ .of(1, 2, 3)
37
+ .pipe(
38
+ auditMap(async (val, { _drain }) => {
39
+ await tp.setTimeout(1)
40
+ return _drain()?.value ?? val
41
+ }),
42
+ rxjs.toArray(),
43
+ )
44
+ .subscribe((val) => {
45
+ t.assert.deepStrictEqual(val, [3])
46
+ })
47
+ })
48
+
49
+ test('auditMap signal', (t) => {
50
+ t.plan(1, { wait: true })
51
+ rxjs
52
+ .of(1, 2, 3)
53
+ .pipe(
54
+ auditMap(async (val, { signal }) => {
55
+ await tp.setTimeout(1)
56
+ assert.strictEqual(signal.aborted, val === 1)
57
+ return val
58
+ }),
59
+ rxjs.toArray(),
60
+ )
61
+ .subscribe((val) => {
62
+ t.assert.deepStrictEqual(val, [1, 3])
63
+ })
64
+ })
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/s3.js CHANGED
@@ -327,7 +327,16 @@ function getTransformedHeaders(headers) {
327
327
  */
328
328
  const transformedHeaders = {}
329
329
 
330
+ const isRangedResponse = headers['content-range'] != null
331
+
330
332
  for (const name of Object.keys(headers)) {
333
+ // Google Cloud S3 returns checksums for the full object
334
+ // even with range requests, which causes checksum mismatches.
335
+ // I assume the checksum headers shouldn't be sent with ranged responses.
336
+ if (isRangedResponse && /^x-amz-checksum-/i.test(name)) {
337
+ continue
338
+ }
339
+
331
340
  const headerValues = headers[name]
332
341
  transformedHeaders[name] = Array.isArray(headerValues)
333
342
  ? headerValues.join(',')
package/serializers.js CHANGED
@@ -140,7 +140,6 @@ export default {
140
140
  ureq && {
141
141
  url: getUrl(ureq),
142
142
  id: ureq.id || getHeader(ureq, 'request-id'),
143
- userAgent: ureq.userAgent ?? getHeader(ureq, 'user-agent'),
144
143
  timing: ureq.timing,
145
144
  method: ureq.method,
146
145
  body: typeof ureq.body === 'string' ? ureq.body : undefined,
@@ -156,7 +155,6 @@ export default {
156
155
  ures: (ures) =>
157
156
  ures && {
158
157
  id: ures.id || getHeader(ures, 'request-id') || getHeader(ures.req, 'request-id'),
159
- userAgent: ures.userAgent ?? getHeader(ures, 'user-agent'),
160
158
  timing: ures.timing,
161
159
  statusCode: ures.statusCode ?? ures.status,
162
160
  bytesRead: ures.bytesRead,