@nxtedition/lib 19.8.1 → 19.8.3

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.
Files changed (3) hide show
  1. package/couch.js +96 -189
  2. package/package.json +18 -17
  3. package/weakCache.d.ts +4 -0
package/couch.js CHANGED
@@ -8,8 +8,7 @@ import { defaultDelay as delay } from './http.js'
8
8
  import urljoin from 'url-join'
9
9
  import undici, { util as undiciUtil } from 'undici'
10
10
  import { AbortError } from './errors.js'
11
- import split2 from 'split2'
12
- import { parse as parseContentType } from 'content-type'
11
+ import { interceptors } from '@nxtedition/nxt-undici'
13
12
 
14
13
  // https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
15
14
  // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
@@ -265,11 +264,7 @@ export function makeCouch(opts) {
265
264
  })
266
265
  }
267
266
 
268
- src = stream.pipeline(
269
- ures.body,
270
- split2('\n', { writableHighWaterMark: highWaterMark ?? 256 * 1024 }),
271
- () => {},
272
- )
267
+ src = ures.body
273
268
 
274
269
  const changes = []
275
270
 
@@ -297,42 +292,59 @@ export function makeCouch(opts) {
297
292
  })
298
293
  .on('close', maybeResume)
299
294
 
295
+ let str = ''
300
296
  while (true) {
301
- const line = src.read()
302
- if (line) {
303
- if (live) {
304
- const data = JSON.parse(line)
305
- if (data.last_seq) {
306
- params.since = data.last_seq
307
- } else {
308
- params.since = data.seq || params.since
309
- changes.push(data)
310
- }
311
- } else {
312
- // NOTE: This makes some assumptions about the format of the JSON.
313
- if (state === 0) {
314
- if (line.endsWith('[')) {
315
- state = 1
297
+ const chunk = src.read()
298
+
299
+ if (chunk !== null) {
300
+ const lines = (str + chunk).split(/\r?\n/)
301
+ str = lines.pop() ?? ''
302
+
303
+ for (const line of lines) {
304
+ if (line === '') {
305
+ // hearbeat
306
+ yield batched ? [] : null
307
+ } else if (line === ',') {
308
+ // Do nothing. Couch sometimes insert new line between
309
+ // json body and comma.
310
+ } else if (live) {
311
+ const data = JSON.parse(line)
312
+ if (data.last_seq) {
313
+ params.since = data.last_seq
316
314
  } else {
317
- assert(false, 'invalid head: ' + line)
315
+ params.since = data.seq || params.since
316
+ changes.push(data)
318
317
  }
319
- } else if (state === 1) {
320
- if (line.startsWith(']')) {
321
- state = 2
322
- } else {
323
- const idx = line.lastIndexOf('}') + 1
324
- assert(idx > 0, 'invalid row: ' + line)
325
- const change = JSON.parse(line.slice(0, idx))
326
- params.since = change.seq || params.since
327
- changes.push(change)
318
+ } else {
319
+ // NOTE: This makes some assumptions about the format of the JSON.
320
+ if (state === 0) {
321
+ if (line.endsWith('[')) {
322
+ state = 1
323
+ } else {
324
+ assert(false, 'invalid head: ' + line)
325
+ }
326
+ } else if (state === 1) {
327
+ if (line.startsWith(']')) {
328
+ state = 2
329
+ } else {
330
+ const idx = line.lastIndexOf('}') + 1
331
+ try {
332
+ assert(idx >= 0, 'invalid row: ' + idx + ' ' + line)
333
+ const change = JSON.parse(line.slice(0, idx))
334
+ params.since = change.seq || params.since
335
+ changes.push(change)
336
+ } catch (err) {
337
+ throw Object.assign(err, { data: line })
338
+ }
339
+ }
340
+ } else if (state === 2) {
341
+ state = 3
342
+ params.since = JSON.parse('{' + line).last_seq
343
+ assert(params.since, 'invalid trailer: ' + line)
328
344
  }
329
- } else if (state === 2) {
330
- state = 3
331
- params.since = JSON.parse('{' + line).last_seq
332
- assert(params.since, 'invalid trailer: ' + line)
333
345
  }
334
346
  }
335
- } else if (changes.length || line === '') {
347
+ } else if (changes.length) {
336
348
  remaining -= changes.length
337
349
  assert(remaining >= 0, 'invalid remaining: ' + remaining)
338
350
 
@@ -887,141 +899,6 @@ function _normalizeAllDocsOptions({
887
899
  }
888
900
  }
889
901
 
890
- class SignalHandler extends undici.DecoratorHandler {
891
- #signal
892
- #handler
893
- #abort
894
-
895
- constructor({ signal, handler }) {
896
- super(handler)
897
- this.#signal = signal
898
- this.#handler = handler
899
- }
900
-
901
- onConnect(abort) {
902
- if (this.#signal?.aborted) {
903
- abort(this.#signal.reason)
904
- } else {
905
- if (this.#signal) {
906
- this.#abort = () => abort(this.#signal.reason)
907
- this.#signal.addEventListener('abort', this.#abort)
908
- }
909
- this.#handler.onConnect?.(abort)
910
- }
911
- }
912
-
913
- onComplete(...args) {
914
- this.#signal?.removeEventListener('abort', this.#abort)
915
- this.#handler.onComplete?.(...args)
916
- }
917
-
918
- onError(...args) {
919
- this.#signal?.removeEventListener('abort', this.#abort)
920
- this.#handler.onError?.(...args)
921
- }
922
- }
923
-
924
- class ErrorHandler extends undici.DecoratorHandler {
925
- #str
926
- #decoder
927
- #statusCode
928
- #contentType
929
- #opts
930
- #handler
931
- #error
932
- #headers
933
-
934
- constructor({ opts, handler }) {
935
- super(handler)
936
-
937
- this.#handler = handler
938
- this.#opts = opts
939
- }
940
-
941
- onConnect(...args) {
942
- this.#str = ''
943
- this.#decoder = null
944
- this.#handler.onConnect?.(...args)
945
- }
946
-
947
- onHeaders(statusCode, headers, resume, statusText) {
948
- this.#statusCode = statusCode
949
-
950
- if (this.#statusCode < 200 || this.#statusCode > 300) {
951
- this.#headers = Array.isArray(headers) ? undiciUtil.parseHeaders(headers) : headers
952
-
953
- this.#error = new Error(statusText ?? statusCode)
954
-
955
- this.#contentType = this.#headers['content-type']
956
- ? parseContentType(this.#headers['content-type'] ?? '')?.type
957
- : null
958
-
959
- if (this.#contentType !== 'application/json' && this.#contentType !== 'plain/text') {
960
- throw this.#error
961
- }
962
-
963
- this.#decoder = new TextDecoder()
964
- } else {
965
- return this.#handler.onHeaders?.(statusCode, headers, resume, statusText)
966
- }
967
- }
968
-
969
- onData(data) {
970
- if (this.#error) {
971
- this.#str += this.#decoder?.decode(data, { stream: true }) ?? ''
972
- } else {
973
- return this.#handler?.onData(data)
974
- }
975
- }
976
-
977
- onComplete(...args) {
978
- if (this.#error) {
979
- this.#str += this.#decoder?.decode(undefined, { stream: false }) ?? ''
980
-
981
- if (this.#contentType === 'application/json') {
982
- let body
983
- let reason
984
- let error
985
- try {
986
- body = JSON.parse(this.#str)
987
- reason = body.reason
988
- error = body.error
989
- } catch {
990
- body = this.#str
991
- // Do nothing...
992
- }
993
-
994
- throw Object.assign(this.#error, {
995
- reason,
996
- error,
997
- data: {
998
- req: this.#opts,
999
- res: {
1000
- body,
1001
- statusCode: this.#statusCode,
1002
- },
1003
- },
1004
- })
1005
- } else if (this.#contentType === 'plain/text') {
1006
- throw Object.assign(this.#error, {
1007
- data: {
1008
- ureq: this.#opts,
1009
- ures: {
1010
- body: this.#str,
1011
- statusCode: this.#statusCode,
1012
- headers: this.#headers,
1013
- },
1014
- },
1015
- })
1016
- }
1017
-
1018
- assert(false)
1019
- } else {
1020
- this.#handler.onComplete?.(...args)
1021
- }
1022
- }
1023
- }
1024
-
1025
902
  class StreamOutput extends stream.Readable {
1026
903
  #str
1027
904
  #resume
@@ -1036,8 +913,8 @@ class StreamOutput extends stream.Readable {
1036
913
  ttfb: -1,
1037
914
  }
1038
915
 
1039
- constructor({ highWaterMark = 256 }) {
1040
- super({ objectMode: true, highWaterMark })
916
+ constructor({ signal, highWaterMark = 256 }) {
917
+ super({ objectMode: true, highWaterMark, signal })
1041
918
  }
1042
919
 
1043
920
  get headers() {
@@ -1136,6 +1013,8 @@ class PromiseOutput {
1136
1013
  #decoder
1137
1014
  #headers
1138
1015
  #startTime = performance.now()
1016
+ #signal
1017
+ #abort
1139
1018
  #stats = {
1140
1019
  connect: -1,
1141
1020
  headers: -1,
@@ -1144,15 +1023,23 @@ class PromiseOutput {
1144
1023
  error: -1,
1145
1024
  }
1146
1025
 
1147
- constructor({ resolve, reject }) {
1026
+ constructor({ resolve, reject, signal }) {
1148
1027
  this.#resolve = resolve
1149
1028
  this.#reject = reject
1029
+ this.#signal = signal
1150
1030
  }
1151
1031
 
1152
- onConnect() {
1032
+ onConnect(abort) {
1153
1033
  this.#stats.connect = performance.now() - this.#startTime
1154
1034
  this.#decoder = new TextDecoder()
1155
1035
  this.#str = ''
1036
+
1037
+ if (this.#signal?.aborted) {
1038
+ abort()
1039
+ } else {
1040
+ this.#abort = abort
1041
+ this.#signal?.addEventListener('abort', this.#abort)
1042
+ }
1156
1043
  }
1157
1044
 
1158
1045
  onHeaders(statusCode, rawHeaders, resume, statusText) {
@@ -1181,9 +1068,8 @@ class PromiseOutput {
1181
1068
 
1182
1069
  this.#str += this.#decoder.decode(undefined, { stream: false })
1183
1070
 
1184
- this.#resolve(
1185
- Object.assign(JSON.parse(this.#str), { headers: this.#headers, stats: this.#stats }),
1186
- )
1071
+ const val = Object.assign(JSON.parse(this.#str), { headers: this.#headers, stats: this.#stats })
1072
+ this.#onDone(null, val)
1187
1073
  }
1188
1074
 
1189
1075
  onError(err) {
@@ -1191,10 +1077,23 @@ class PromiseOutput {
1191
1077
  this.#stats.error = performance.now() - this.#startTime - this.#stats.data
1192
1078
  }
1193
1079
 
1194
- this.#reject(err)
1080
+ this.#onDone(err)
1081
+ }
1082
+
1083
+ #onDone(err, val) {
1084
+ if (err) {
1085
+ this.#reject(err)
1086
+ } else {
1087
+ this.#resolve(val)
1088
+ }
1089
+
1090
+ if (this.#abort) {
1091
+ this.#signal?.removeEventListener('abort', this.#abort)
1092
+ }
1195
1093
  }
1196
1094
  }
1197
1095
 
1096
+ const dispatcherCache = new WeakMap()
1198
1097
  export function request(
1199
1098
  url,
1200
1099
  {
@@ -1204,6 +1103,7 @@ export function request(
1204
1103
  headers,
1205
1104
  dispatcher = undici.getGlobalDispatcher(),
1206
1105
  signal,
1106
+ logger,
1207
1107
  stream,
1208
1108
  },
1209
1109
  ) {
@@ -1221,26 +1121,33 @@ export function request(
1221
1121
  headers: {
1222
1122
  'content-type': body != null && typeof body === 'object' ? 'application/json' : 'plain/text',
1223
1123
  'user-agent': globalThis.userAgent ?? 'nxt-lib',
1224
- 'request-id': genReqId(),
1225
1124
  accept: 'application/json',
1226
1125
  ...headers,
1227
1126
  },
1127
+ logger,
1228
1128
  body: body != null && typeof body === 'object' ? JSON.stringify(body) : body,
1229
1129
  }
1230
1130
 
1231
- dispatcher = dispatcher.compose(
1232
- (dispatch) => (opts, handler) => dispatch(opts, new ErrorHandler({ opts, handler })),
1233
- (dispatch) => (opts, handler) => dispatch(opts, new SignalHandler({ signal, handler })),
1234
- )
1131
+ let wrappedDispatcher = dispatcherCache.get(dispatcher)
1132
+ if (!wrappedDispatcher) {
1133
+ wrappedDispatcher = dispatcher.compose(
1134
+ interceptors.responseError,
1135
+ interceptors.log,
1136
+ interceptors.requestId,
1137
+ interceptors.responseRetry,
1138
+ interceptors.responseVerify,
1139
+ )
1140
+ dispatcherCache.set(dispatcher, wrappedDispatcher)
1141
+ }
1235
1142
 
1236
1143
  if (stream) {
1237
- const handler = new StreamOutput(stream)
1238
- dispatcher.dispatch(opts, handler)
1144
+ const handler = new StreamOutput({ signal, ...stream })
1145
+ wrappedDispatcher.dispatch(opts, handler)
1239
1146
  return handler
1240
1147
  } else {
1241
1148
  return new Promise((resolve, reject) => {
1242
- const handler = new PromiseOutput({ resolve, reject })
1243
- dispatcher.dispatch(opts, handler)
1149
+ const handler = new PromiseOutput({ resolve, reject, signal })
1150
+ wrappedDispatcher.dispatch(opts, handler)
1244
1151
  })
1245
1152
  }
1246
1153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "19.8.1",
3
+ "version": "19.8.3",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -26,6 +26,7 @@
26
26
  "timers.js",
27
27
  "trace.js",
28
28
  "weakCache.js",
29
+ "weakCache.d.ts",
29
30
  "couch.js",
30
31
  "app.js",
31
32
  "errors.js",
@@ -74,13 +75,14 @@
74
75
  }
75
76
  },
76
77
  "eslintIgnore": [
77
- "/__tests__"
78
+ "/__tests__",
79
+ "**/*.d.ts"
78
80
  ],
79
81
  "dependencies": {
80
- "@aws-sdk/client-s3": "^3.554.0",
81
- "@elastic/elasticsearch": "^8.13.1",
82
- "@elastic/transport": "^8.5.1",
83
- "@nxtedition/nxt-undici": "^2.0.46",
82
+ "@aws-sdk/client-s3": "^3.600.0",
83
+ "@elastic/elasticsearch": "^8.14.0",
84
+ "@elastic/transport": "^8.6.0",
85
+ "@nxtedition/nxt-undici": "^2.2.7",
84
86
  "content-type": "^1.0.5",
85
87
  "date-fns": "^3.6.0",
86
88
  "fast-querystring": "^1.1.1",
@@ -95,31 +97,30 @@
95
97
  "nested-error-stacks": "^2.1.1",
96
98
  "object-hash": "^3.0.0",
97
99
  "p-queue": "^8.0.1",
98
- "pino": "^8.20.0",
100
+ "pino": "^9.2.0",
99
101
  "qs": "^6.12.1",
100
102
  "request-target": "^1.0.2",
101
- "smpte-timecode": "^1.3.5",
103
+ "smpte-timecode": "^1.3.6",
102
104
  "split-string": "^6.0.0",
103
- "split2": "^4.2.0",
104
105
  "toobusy-js": "^0.5.1",
105
- "undici": "^6.13.0",
106
+ "undici": "^6.19.2",
106
107
  "url-join": "^5.0.0"
107
108
  },
108
109
  "devDependencies": {
109
- "@nxtedition/deepstream.io-client-js": ">=24.1.20",
110
- "@types/lodash": "^4.17.0",
111
- "@types/node": "^20.12.7",
110
+ "@nxtedition/deepstream.io-client-js": ">=24.2.4",
111
+ "@types/lodash": "^4.17.5",
112
+ "@types/node": "^20.14.6",
112
113
  "eslint": "^8.0.0",
113
114
  "eslint-config-prettier": "^9.1.0",
114
115
  "eslint-config-standard": "^17.0.0",
115
116
  "eslint-plugin-import": "^2.29.1",
116
- "eslint-plugin-n": "^17.2.1",
117
+ "eslint-plugin-n": "^17.9.0",
117
118
  "eslint-plugin-node": "^11.1.0",
118
- "eslint-plugin-promise": "^6.0.0",
119
+ "eslint-plugin-promise": "^6.2.0",
119
120
  "husky": "^9.0.11",
120
- "lint-staged": "^15.2.2",
121
+ "lint-staged": "^15.2.7",
121
122
  "pinst": "^3.0.0",
122
- "prettier": "^3.2.5",
123
+ "prettier": "^3.3.2",
123
124
  "rxjs": "^7.5.6",
124
125
  "send": "^0.18.0"
125
126
  },
package/weakCache.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export function makeWeakCache<FactoryArgs extends any[], CachedType>(
2
+ valueSelector: (...args: FactoryArgs) => CachedType,
3
+ keySelector?: (...args: FactoryArgs) => string,
4
+ ): (...args: FactoryArgs) => CachedType