@nxtedition/lib 26.8.8 → 27.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
@@ -1,3 +1,4 @@
1
+ import { readFileSync } from 'node:fs'
1
2
  import * as inspector from 'node:inspector'
2
3
  import fs from 'node:fs'
3
4
  import os from 'node:os'
@@ -7,9 +8,9 @@ import assert from 'node:assert'
7
8
  import cluster from 'node:cluster'
8
9
  import stream from 'node:stream'
9
10
  import { Buffer } from 'node:buffer'
10
- import { getDockerSecretsSync } from './docker-secrets.js'
11
- import { getUTCRangeForLocalTime } from './time.js'
12
- import fp from 'lodash/fp.js'
11
+ import v8 from 'node:v8'
12
+ import { isPrimary } from 'node:cluster'
13
+ import { monitorEventLoopDelay } from 'node:perf_hooks'
13
14
  import {
14
15
  isMainThread,
15
16
  parentPort,
@@ -17,13 +18,16 @@ import {
17
18
  BroadcastChannel,
18
19
  resourceLimits,
19
20
  } from 'node:worker_threads'
21
+
22
+ import { getDockerSecretsSync } from './docker-secrets.js'
23
+ import { getUTCRangeForLocalTime } from './time.js'
24
+ import fp from 'lodash/fp.js'
20
25
  import deepstream from '@nxtedition/deepstream.io-client-js'
21
26
  import { createLogger } from './logger.js'
22
27
  import nconf from 'nconf'
23
28
  import { makeCouch } from './couch.js'
24
- import { makeTemplateCompiler } from './util/template/index.js'
29
+ import { makeTemplateCompiler } from '@nxtedition/template'
25
30
  import { makeDeepstream } from './deepstream.js'
26
- import v8 from 'v8'
27
31
  import * as rxjs from 'rxjs'
28
32
  import rx from 'rxjs/operators'
29
33
  import { performance } from 'perf_hooks'
@@ -31,15 +35,18 @@ import hashString from './hash.js'
31
35
  import { makeTrace } from './trace.js'
32
36
  import { compose, createServer } from './http.js'
33
37
  import { json } from 'node:stream/consumers'
34
- import { monitorEventLoopDelay } from 'node:perf_hooks'
35
38
  import xuid from 'xuid'
36
39
  import { isTimeBetween } from './time.js'
37
40
  import makeUnderPressure from './under-pressure.js'
38
- import { isPrimary } from 'node:cluster'
39
41
  import { nice } from '@nxtedition/sched'
40
42
  import { setAffinity } from './numa.js'
41
43
 
42
- export function makeApp(appConfig, onTerminate) {
44
+ /**
45
+ * @param {object} appConfig
46
+ * @param {object} [meta]
47
+ * @returns {object}
48
+ */
49
+ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
43
50
  let ds
44
51
  let nxt
45
52
  let couch
@@ -49,6 +56,27 @@ export function makeApp(appConfig, onTerminate) {
49
56
  let logger
50
57
  let trace
51
58
 
59
+ let onTerminate
60
+ let meta
61
+
62
+ if (typeof onTerminateOrMeta === 'function') {
63
+ meta = metaOrNull
64
+ } else {
65
+ meta = onTerminateOrMeta ?? metaOrNull
66
+ }
67
+
68
+ if (onTerminate != null && typeof onTerminateOrMeta !== 'function') {
69
+ throw new Error('onTerminate must be a function or null')
70
+ }
71
+
72
+ if (meta != null && typeof meta !== 'object') {
73
+ throw new Error('meta must be an object or null')
74
+ }
75
+
76
+ const pkg = meta?.url
77
+ ? JSON.parse(readFileSync(new URL('../package.json', meta.url).pathname).toString())
78
+ : null
79
+
52
80
  /** @type {Array<rxjs.Subscription|Function|AsyncFunction|Disposable|AsyncDisposable>} */
53
81
  const destroyers = []
54
82
 
@@ -119,6 +147,9 @@ export function makeApp(appConfig, onTerminate) {
119
147
  }
120
148
  }
121
149
 
150
+ appConfig.name ||= pkg?.name ?? null
151
+ appConfig.version ||= pkg?.version ?? null
152
+
122
153
  assert(appConfig.name, 'name is required')
123
154
 
124
155
  const appDestroyers = []
package/couch.d.ts CHANGED
@@ -21,6 +21,7 @@ export interface CouchRequestOptions<Stream extends boolean = false> {
21
21
  bodyTimeout?: number
22
22
  dispatcher?: Dispatcher
23
23
  query?: {
24
+ key?: string
24
25
  startkey?: string
25
26
  endkey?: string
26
27
  update?: boolean
package/couch.js CHANGED
@@ -2,7 +2,7 @@ import assert from 'node:assert'
2
2
  import stream from 'node:stream'
3
3
  import querystring from 'node:querystring'
4
4
  import createError from 'http-errors'
5
- import { makeWeakCache } from './weakCache.js'
5
+ import { makeWeakCache } from '@nxtedition/weak-cache'
6
6
  import { defaultDelay as delay } from './http.js'
7
7
  import urljoin from 'url-join'
8
8
  import { AbortError } from './errors.js'
package/numa.js CHANGED
@@ -3,6 +3,21 @@ import path from 'node:path'
3
3
 
4
4
  import { sched_setaffinity } from '@nxtedition/sched'
5
5
 
6
+ function parseRange(value) {
7
+ const range = []
8
+ for (const part of value.split(',')) {
9
+ if (part.includes('-')) {
10
+ const [start, end] = part.split('-').map(Number)
11
+ for (let i = start; i <= end; i++) {
12
+ range.push(i)
13
+ }
14
+ } else {
15
+ range.push(Number(part))
16
+ }
17
+ }
18
+ return range
19
+ }
20
+
6
21
  /**
7
22
  *
8
23
  * @param {number|number[]} numa
@@ -14,6 +29,8 @@ export function setAffinity(numa) {
14
29
  throw new Error('NUMA node must be a non-negative integer')
15
30
  }
16
31
 
32
+ const isolated = parseRange(fs.readFileSync('/sys/devices/system/cpu/isolated', 'utf8').trim())
33
+
17
34
  const allNodes = []
18
35
  for (const entry of fs.readdirSync('/sys/devices/system/node')) {
19
36
  if (!entry.startsWith('node')) {
@@ -23,25 +40,17 @@ export function setAffinity(numa) {
23
40
  const cpulist = fs
24
41
  .readFileSync(path.join('/sys/devices/system/node', entry, 'cpulist'), 'utf8')
25
42
  .trim()
26
- const cpus = []
27
- for (const part of cpulist.split(',')) {
28
- if (part.includes('-')) {
29
- const [start, end] = part.split('-').map(Number)
30
- for (let i = start; i <= end; i++) {
31
- cpus.push(i)
32
- }
33
- } else {
34
- cpus.push(Number(part))
35
- }
36
- }
37
- allNodes.push(cpus)
43
+
44
+ allNodes.push(parseRange(cpulist))
38
45
  }
39
46
 
40
47
  if (allNodes.length === 0) {
41
48
  throw new Error('No NUMA nodes found')
42
49
  }
43
50
 
44
- const affinity = indices.flatMap((i) => allNodes[i % allNodes.length] ?? [])
51
+ const affinity = indices
52
+ .flatMap((i) => allNodes[i % allNodes.length] ?? [])
53
+ .filter((cpu) => !isolated.includes(cpu))
45
54
  sched_setaffinity(0, affinity)
46
55
  globalThis.__nxt_sched_affinity = {
47
56
  cpulist: affinity,
package/package.json CHANGED
@@ -1,25 +1,22 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.8.8",
4
- "license": "MIT",
3
+ "version": "27.0.0",
4
+ "license": "UNLICENSED",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "index.d.ts",
9
9
  "hash.js",
10
10
  "ass.js",
11
- "rxjs/*",
12
11
  "util/*",
13
12
  "cache.js",
14
13
  "fixed-queue.js",
15
- "http-client.js",
16
14
  "subtract-ranges.js",
17
15
  "serializers.js",
18
16
  "platform.js",
19
17
  "elasticsearch.js",
20
18
  "merge-ranges.js",
21
19
  "http.js",
22
- "s3.js",
23
20
  "time.js",
24
21
  "mutex.js",
25
22
  "deepstream.js",
@@ -33,8 +30,6 @@
33
30
  "proxy.js",
34
31
  "timers.js",
35
32
  "trace.js",
36
- "weakCache.js",
37
- "weakCache.d.ts",
38
33
  "couch.js",
39
34
  "couch.d.ts",
40
35
  "app.js",
@@ -43,57 +38,34 @@
43
38
  "errors.d.ts",
44
39
  "worker.js",
45
40
  "stream.js",
46
- "timeline.js",
47
41
  "transcript.js",
48
42
  "docker-secrets.js",
49
43
  "wordwrap.js",
50
- "under-pressure.js",
51
- "yield.js"
44
+ "under-pressure.js"
52
45
  ],
53
46
  "scripts": {
54
47
  "test": "node --test-timeout 60000 --test",
55
- "test:types": "tsd",
56
- "prepare": "husky"
57
- },
58
- "lint-staged": {
59
- "*.{js,jsx,md,ts}": [
60
- "eslint",
61
- "prettier --write"
62
- ]
63
- },
64
- "prettier": {
65
- "printWidth": 100,
66
- "semi": false,
67
- "singleQuote": true
48
+ "test:types": "tsd"
68
49
  },
69
50
  "dependencies": {
70
- "@aws-sdk/client-s3": "3.918.0",
71
51
  "@elastic/elasticsearch": "^8.17.1",
72
52
  "@elastic/transport": "^8.9.3",
73
53
  "@nxtedition/nxt-undici": "^6.4.17",
74
54
  "@nxtedition/sched": "^1.0.2",
75
- "@smithy/node-http-handler": "^4.4.3",
76
- "acorn": "^8.15.0",
77
- "astring": "^1.9.0",
78
- "date-fns": "^4.1.0",
55
+ "@nxtedition/template": "^1.0.0-alpha.0",
56
+ "@nxtedition/weak-cache": "^1.0.0-alpha.0",
79
57
  "diff": "5.2.0",
80
58
  "fast-querystring": "^1.1.2",
81
- "hasha": "^7.0.0",
82
59
  "http-errors": "^2.0.0",
83
- "json5": "^2.2.3",
84
60
  "lodash": "^4.17.21",
85
61
  "lru-cache": "^11.2.2",
86
62
  "mime": "^4.0.7",
87
63
  "mitata": "^1.0.34",
88
- "moment-timezone": "^0.5.48",
89
64
  "nconf": "^0.13.0",
90
65
  "object-hash": "^3.0.0",
91
- "p-queue": "^9.0.0",
92
66
  "pino": "^10.1.0",
93
67
  "qs": "^6.14.0",
94
68
  "request-target": "^1.0.2",
95
- "smpte-timecode": "^1.3.6",
96
- "split-string": "^6.0.0",
97
69
  "url-join": "^5.0.0",
98
70
  "xuid": "^4.1.5",
99
71
  "yocto-queue": "^1.2.1"
@@ -103,20 +75,9 @@
103
75
  "@types/lodash": "^4.17.20",
104
76
  "@types/node": "^24.9.1",
105
77
  "canvas": "^3.1.0",
106
- "eslint": "^9.38.0",
107
- "eslint-config-prettier": "^10.1.8",
108
- "eslint-config-standard": "^17.0.0",
109
- "eslint-plugin-import": "^2.32.0",
110
- "eslint-plugin-n": "^17.23.1",
111
- "eslint-plugin-node": "^11.1.0",
112
- "eslint-plugin-promise": "^7.2.1",
113
- "husky": "^9.1.7",
114
- "lint-staged": "^16.2.6",
115
- "prettier": "^3.6.2",
116
78
  "rxjs": "^7.8.2",
117
79
  "tsd": "^0.33.0",
118
- "typescript": "^5.9.3",
119
- "typescript-eslint": "^8.46.2"
80
+ "typescript": "^5.9.3"
120
81
  },
121
82
  "peerDependencies": {
122
83
  "@elastic/elasticsearch": "^8.6.0",
@@ -124,6 +85,7 @@
124
85
  "@nxtedition/deepstream.io-client-js": ">=14.1.0",
125
86
  "canvas": "^3.1.0",
126
87
  "pino": ">=7.0.0",
127
- "rxjs": ">=6.0.0"
128
- }
88
+ "rxjs": "^7.0.0"
89
+ },
90
+ "gitHead": "8c7a9f6b7dbf185d39f5927c779265415fb29bfb"
129
91
  }
package/rxjs/auditMap.js DELETED
@@ -1,111 +0,0 @@
1
- import { Observable, from } from 'rxjs'
2
-
3
- function auditMapImpl(project) {
4
- return new Observable((o) => {
5
- let pendingValue = null
6
- let hasPendingValue = false
7
- let isComplete = false
8
- let abortController = null
9
-
10
- let innerSubscription = null
11
- let outerSubscription = null
12
-
13
- function _error(err) {
14
- o.error(err)
15
- }
16
-
17
- function _innerComplete() {
18
- innerSubscription = null
19
- abortController = null
20
-
21
- if (hasPendingValue) {
22
- const value = pendingValue
23
- pendingValue = null
24
- hasPendingValue = false
25
- _tryNext(value)
26
- } else if (isComplete) {
27
- o.complete()
28
- }
29
- }
30
-
31
- function _innerNext(val) {
32
- o.next(val)
33
- }
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
-
45
- function _tryNext(value) {
46
- try {
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
- })
59
- const observable = typeof result.then === 'function' ? from(result) : result
60
- innerSubscription = observable.subscribe({
61
- next: _innerNext,
62
- error: _error,
63
- complete: _innerComplete,
64
- })
65
- if (innerSubscription && innerSubscription.closed) {
66
- innerSubscription = null
67
- }
68
- } catch (err) {
69
- o.error(err)
70
- }
71
- }
72
-
73
- function _next(value) {
74
- if (innerSubscription) {
75
- pendingValue = value
76
- hasPendingValue = true
77
- abortController?.abort()
78
- } else {
79
- _tryNext(value)
80
- }
81
- }
82
-
83
- function _complete() {
84
- isComplete = true
85
- if (!innerSubscription) {
86
- o.complete()
87
- } else {
88
- abortController?.abort()
89
- }
90
- }
91
-
92
- outerSubscription = this.subscribe({
93
- next: _next,
94
- error: _error,
95
- complete: _complete,
96
- })
97
-
98
- return () => {
99
- if (innerSubscription) {
100
- innerSubscription.unsubscribe()
101
- }
102
- outerSubscription.unsubscribe()
103
- }
104
- })
105
- }
106
-
107
- Observable.prototype.auditMap = auditMapImpl
108
-
109
- export default function auditMap(project) {
110
- return (o) => auditMapImpl.call(o, project)
111
- }
@@ -1,64 +0,0 @@
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
- })
@@ -1,6 +0,0 @@
1
- import type { ObservableInput, OperatorFunction } from 'rxjs'
2
-
3
- export default function combineMap<T, R>(
4
- project: (value: T) => ObservableInput<R>,
5
- equals?: (a: T, b: T) => boolean,
6
- ): OperatorFunction<T[], R[]>
@@ -1,152 +0,0 @@
1
- import { Observable, from, throwError } from 'rxjs'
2
-
3
- const EMPTY = Object.freeze([])
4
-
5
- function combineMapImpl(project, equals = (a, b) => a === b) {
6
- return new Observable((o) => {
7
- let curr = EMPTY
8
- let scheduled = false
9
- let disposed = false
10
- let dirty = false
11
- let active = 0
12
- let empty = 0
13
-
14
- const _error = (err) => o.error(err)
15
-
16
- function _update() {
17
- scheduled = false
18
-
19
- if (empty > 0) {
20
- return
21
- }
22
-
23
- if (dirty) {
24
- dirty = false
25
- o.next(curr.map(({ value }) => value))
26
- }
27
-
28
- if (active === 0) {
29
- o.complete()
30
- }
31
- }
32
-
33
- function update() {
34
- if (!scheduled && !disposed) {
35
- scheduled = true
36
- queueMicrotask(_update)
37
- }
38
- }
39
-
40
- active += 1
41
- const subscription = this.subscribe({
42
- next(keys) {
43
- keys = Array.isArray(keys) ? keys : EMPTY
44
-
45
- // TODO (perf): Avoid array allocation & copy if nothing has updated.
46
- const prev = curr
47
- curr = new Array(keys.length)
48
-
49
- const prevLen = prev.length
50
- const currLen = curr.length
51
-
52
- if (currLen !== prevLen || prev === EMPTY) {
53
- dirty = true
54
- }
55
-
56
- for (let n = 0; n < currLen; ++n) {
57
- const key = keys[n]
58
-
59
- if (n < prevLen && prev[n] && equals(prev[n].key, key)) {
60
- curr[n] = prev[n]
61
- prev[n] = null
62
- continue
63
- }
64
-
65
- dirty = true
66
-
67
- // TODO (perf): Guess start index based on n, e.g. n - 1 and n + 1 to check if
68
- // a key has simply been added or removed.
69
- const i = prev.findIndex((entry) => entry && equals(entry.key, key))
70
-
71
- if (i !== -1) {
72
- curr[n] = prev[i]
73
- prev[i] = null
74
- } else {
75
- let observable
76
- try {
77
- observable = from(project(keys[n]))
78
- } catch (err) {
79
- observable = throwError(() => err)
80
- }
81
-
82
- const entry = {
83
- key,
84
- value: EMPTY,
85
- subscription: null,
86
- }
87
-
88
- empty += 1
89
- entry.subscription = observable.subscribe({
90
- next(value) {
91
- if (entry.value === EMPTY) {
92
- empty -= 1
93
- }
94
-
95
- entry.value = value
96
- dirty = true
97
-
98
- update()
99
- },
100
- error: _error,
101
- })
102
-
103
- active += 1
104
- entry.subscription.add(() => {
105
- active -= 1
106
-
107
- if (entry.value === EMPTY) {
108
- empty -= 1
109
- }
110
-
111
- update()
112
- })
113
-
114
- if (disposed) {
115
- entry.subscription.unsubscribe()
116
- } else {
117
- curr[n] = entry
118
- }
119
- }
120
- }
121
-
122
- // TODO (perf): start from index where prev[n] is not null.
123
- for (let n = 0; n < prevLen; n++) {
124
- prev[n]?.subscription?.unsubscribe()
125
- }
126
-
127
- update()
128
- },
129
- complete: () => {
130
- active -= 1
131
- update()
132
- },
133
- error: _error,
134
- })
135
-
136
- return () => {
137
- disposed = true
138
-
139
- for (const entry of curr) {
140
- entry?.subscription?.unsubscribe()
141
- }
142
-
143
- subscription.unsubscribe()
144
- }
145
- })
146
- }
147
-
148
- Observable.prototype.combineMap = combineMapImpl
149
-
150
- export default function combineMap(project, equals) {
151
- return (o) => combineMapImpl.call(o, project, equals)
152
- }