@nxtedition/lib 19.1.0 → 19.1.1

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 (2) hide show
  1. package/couch.js +175 -92
  2. package/package.json +7 -7
package/couch.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import assert from 'node:assert'
2
+ import stream from 'node:stream'
2
3
  import querystring from 'node:querystring'
3
4
  import createError from 'http-errors'
4
5
  import { makeWeakCache } from './weakCache.js'
@@ -8,7 +9,6 @@ import urljoin from 'url-join'
8
9
  import undici from 'undici'
9
10
  import { AbortError } from './errors.js'
10
11
  import split2 from 'split2'
11
- import stream from 'node:stream'
12
12
 
13
13
  // https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
14
14
  // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
@@ -886,26 +886,21 @@ function _normalizeAllDocsOptions({
886
886
  }
887
887
  }
888
888
 
889
- class Handler {
890
- #state = 0
891
- #str = ''
892
- #decoder = new TextDecoder()
893
- #resolve
894
- #reject
895
- #resume
896
- #abort
889
+ class Handler extends undici.DecoratorHandler {
890
+ #str
891
+ #decoder
897
892
  #onAbort
898
893
  #signal
899
- #next
900
894
  #statusCode
901
895
  #statusText
902
896
  #opts
897
+ #handler
898
+
899
+ constructor({ opts, signal, handler }) {
900
+ super(handler)
903
901
 
904
- constructor({ opts, signal, resolve, reject, next }) {
905
- this.#next = next
902
+ this.#handler = handler
906
903
  this.#opts = opts
907
- this.#resolve = resolve
908
- this.#reject = reject
909
904
  this.#signal = signal
910
905
  }
911
906
 
@@ -913,101 +908,64 @@ class Handler {
913
908
  if (this.#signal?.aborted) {
914
909
  abort(this.#signal.reason)
915
910
  } else {
916
- this.#abort = abort
917
911
  if (this.#signal) {
918
912
  this.#onAbort = () => abort(this.#signal.reason)
919
913
  this.#signal.addEventListener('abort', this.#onAbort)
920
914
  }
915
+
916
+ this.#str = ''
917
+ this.#decoder = null
918
+ this.#handler.onConnect?.(abort)
921
919
  }
922
920
  }
923
921
 
924
922
  onHeaders(statusCode, rawHeaders, resume, statusText) {
925
923
  this.#statusCode = statusCode
926
924
  this.#statusText = statusText
927
- this.#resume = resume
928
925
 
929
926
  if (this.#statusCode >= 200 && this.#statusCode < 300) {
930
- this.#next = null
927
+ this.#handler.onHeaders?.(statusCode, rawHeaders, resume, statusText)
928
+ } else {
929
+ this.#decoder = new TextDecoder()
931
930
  }
932
931
  }
933
932
 
934
933
  onData(data) {
935
- if (!this.#next) {
934
+ if (this.#decoder) {
936
935
  this.#str += this.#decoder.decode(data, { stream: true })
937
936
  } else {
938
- const lines = this.#decoder.decode(data, { stream: true }).split(/\r?\n+/)
939
- lines[0] = this.#str + lines[0]
940
- this.#str = lines.pop() ?? ''
941
-
942
- const rows = []
943
- for (const line of lines) {
944
- if (this.#state === 0) {
945
- if (line.endsWith('[')) {
946
- this.#state = 1
947
- } else {
948
- throw new Error('unexpected data')
949
- }
950
- } else if (this.#state === 1) {
951
- if (line.startsWith(']')) {
952
- this.#state = 2
953
- } else {
954
- rows.push(JSON.parse(line.slice(0, line.lastIndexOf('}') + 1)))
955
- }
956
- }
957
- }
958
-
959
- if (rows.length === 0) {
960
- return true
961
- }
962
-
963
- const thenable = this.#next(rows)
964
-
965
- if (!thenable) {
966
- return true
967
- }
968
-
969
- thenable.then(this.#resume, this.#abort)
970
-
971
- return false
937
+ return this.#handler?.onData(data)
972
938
  }
973
939
  }
974
940
 
975
- onComplete() {
976
- assert(!this.#next || this.#state === 2)
977
-
978
- if (!this.#next) {
979
- this.#str += this.#decoder.decode(undefined, { stream: false })
980
-
981
- if (this.#statusCode >= 200 && this.#statusCode < 300) {
982
- this.onFinally(null, this.#next ? null : JSON.parse(this.#str))
983
- } else {
984
- let body
985
- let reason
986
- let error
987
- try {
988
- body = JSON.parse(this.#str)
989
- reason = body.reason
990
- error = body.error
991
- } catch {
992
- // Do nothing...
993
- }
941
+ onComplete(trailers) {
942
+ if (this.#decoder) {
943
+ let body
944
+ let reason
945
+ let error
946
+ try {
947
+ body = JSON.parse(this.#str)
948
+ reason = body.reason
949
+ error = body.error
950
+ } catch {
951
+ // Do nothing...
952
+ }
994
953
 
995
- this.onFinally(
996
- Object.assign(new Error(this.#statusText ?? reason), {
997
- reason,
998
- error,
999
- data: {
1000
- req: this.#opts,
1001
- res: {
1002
- body,
1003
- statusCode: this.#statusCode,
1004
- },
954
+ this.onFinally(
955
+ Object.assign(new Error(this.#statusText ?? reason), {
956
+ reason,
957
+ error,
958
+ data: {
959
+ req: this.#opts,
960
+ res: {
961
+ body,
962
+ statusCode: this.#statusCode,
1005
963
  },
1006
- }),
1007
- )
1008
- }
964
+ },
965
+ }),
966
+ )
1009
967
  } else {
1010
- this.onFinally(null)
968
+ this.onFinally(null, trailers)
1011
969
  }
1012
970
  }
1013
971
 
@@ -1021,14 +979,127 @@ class Handler {
1021
979
  }
1022
980
 
1023
981
  if (err) {
1024
- this.#reject(err)
982
+ this.#handler.onError?.(err)
983
+ } else {
984
+ this.#handler.onComplete?.(val)
985
+ }
986
+ }
987
+ }
988
+
989
+ class StreamOutput extends stream.Readable {
990
+ #str
991
+ #resume
992
+ #abort
993
+ #state
994
+ #decoder
995
+ #didPush = false
996
+
997
+ constructor({ highWaterMark }) {
998
+ super({ objectMode: true, highWaterMark })
999
+ }
1000
+
1001
+ _read() {
1002
+ this.#resume?.()
1003
+ }
1004
+
1005
+ _destroy(err, callback) {
1006
+ this.#abort?.(err)
1007
+ callback(err)
1008
+ }
1009
+
1010
+ onConnect(abort) {
1011
+ if (this.destroyed) {
1012
+ abort(this.errored)
1013
+ } else if (!this.#didPush) {
1014
+ this.#state = 0
1015
+ this.#str = ''
1016
+ this.#abort = abort
1017
+ this.#decoder = new TextDecoder()
1025
1018
  } else {
1026
- this.#resolve(val)
1019
+ throw new Error('unusable')
1020
+ }
1021
+ }
1022
+
1023
+ onHeaders(statusCode, rawHeaders, resume, statusText) {
1024
+ if (statusCode >= 300 || statusCode < 200) {
1025
+ throw new Error('invalid status code: ' + statusCode)
1026
+ }
1027
+
1028
+ this.#resume = resume
1029
+ }
1030
+
1031
+ onData(data) {
1032
+ const lines = this.#decoder.decode(data, { stream: true }).split(/\r?\n+/)
1033
+ lines[0] = this.#str + lines[0]
1034
+ this.#str = lines.pop() ?? ''
1035
+
1036
+ for (const line of lines) {
1037
+ if (this.#state === 0) {
1038
+ if (line.endsWith('[')) {
1039
+ this.#state = 1
1040
+ } else {
1041
+ throw new Error('unexpected data')
1042
+ }
1043
+ } else if (this.#state === 1) {
1044
+ if (line.startsWith(']')) {
1045
+ this.#state = 2
1046
+ } else {
1047
+ this.push(JSON.parse(line.slice(0, line.lastIndexOf('}') + 1)))
1048
+ this.#didPush = true
1049
+ }
1050
+ }
1027
1051
  }
1052
+
1053
+ return this.readableLength < this.readableHighWaterMark
1054
+ }
1055
+
1056
+ onComplete() {
1057
+ assert(this.#state === 2)
1058
+ this.push(null)
1059
+ }
1060
+
1061
+ onError(err) {
1062
+ this.destroy(err)
1028
1063
  }
1029
1064
  }
1030
1065
 
1031
- export async function request(
1066
+ class PromiseOutput {
1067
+ #resolve
1068
+ #reject
1069
+ #str
1070
+ #decoder
1071
+
1072
+ constructor({ resolve, reject }) {
1073
+ this.#resolve = resolve
1074
+ this.#reject = reject
1075
+ }
1076
+
1077
+ onConnect() {
1078
+ this.#decoder = new TextDecoder()
1079
+ this.#str = ''
1080
+ }
1081
+
1082
+ onHeaders(statusCode, rawHeaders, resume, statusText) {
1083
+ if (statusCode >= 300 || statusCode < 200) {
1084
+ throw new Error('invalid status code: ' + statusCode)
1085
+ }
1086
+ }
1087
+
1088
+ onData(data) {
1089
+ this.#str += this.#decoder.decode(data, { stream: true })
1090
+ }
1091
+
1092
+ onComplete() {
1093
+ this.#str += this.#decoder.decode(undefined, { stream: false })
1094
+ this.#resolve(JSON.parse(this.#str))
1095
+ }
1096
+
1097
+ onError(err) {
1098
+ this.#reject(err)
1099
+ }
1100
+ }
1101
+
1102
+ export function request(
1032
1103
  url,
1033
1104
  {
1034
1105
  body,
@@ -1037,8 +1108,8 @@ export async function request(
1037
1108
  headers,
1038
1109
  dispatcher = undici.getGlobalDispatcher(),
1039
1110
  signal,
1111
+ stream,
1040
1112
  },
1041
- next,
1042
1113
  ) {
1043
1114
  url = new URL(url)
1044
1115
  if (url.search) {
@@ -1055,7 +1126,19 @@ export async function request(
1055
1126
  },
1056
1127
  body: body != null && typeof body === 'object' ? JSON.stringify(body) : body,
1057
1128
  }
1058
- return new Promise((resolve, reject) => {
1059
- dispatcher.dispatch(opts, new Handler({ opts, signal, resolve, reject, next }))
1060
- })
1129
+
1130
+ dispatcher = dispatcher.compose(
1131
+ (dispatch) => (opts, handler) => dispatch(opts, new Handler({ opts, signal, handler })),
1132
+ )
1133
+
1134
+ if (stream) {
1135
+ const handler = new StreamOutput({ highWaterMark: stream.highWaterMark ?? 128 })
1136
+ dispatcher.dispatch(opts, handler)
1137
+ return handler
1138
+ } else {
1139
+ return new Promise((resolve, reject) => {
1140
+ const handler = new PromiseOutput({ resolve, reject })
1141
+ dispatcher.dispatch(opts, handler)
1142
+ })
1143
+ }
1061
1144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "19.1.0",
3
+ "version": "19.1.1",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -75,10 +75,10 @@
75
75
  "/__tests__"
76
76
  ],
77
77
  "dependencies": {
78
- "@aws-sdk/client-s3": "^3.552.0",
78
+ "@aws-sdk/client-s3": "^3.554.0",
79
79
  "@elastic/elasticsearch": "^8.13.1",
80
- "@elastic/transport": "^8.5.0",
81
- "@nxtedition/nxt-undici": "^2.0.45",
80
+ "@elastic/transport": "^8.5.1",
81
+ "@nxtedition/nxt-undici": "^2.0.46",
82
82
  "date-fns": "^3.6.0",
83
83
  "fast-querystring": "^1.1.1",
84
84
  "hasha": "^6.0.0",
@@ -94,13 +94,13 @@
94
94
  "p-queue": "^8.0.1",
95
95
  "pino": "^8.20.0",
96
96
  "pino-std-serializers": "^6.2.2",
97
- "qs": "^6.12.0",
97
+ "qs": "^6.12.1",
98
98
  "request-target": "^1.0.2",
99
99
  "smpte-timecode": "^1.3.5",
100
100
  "split-string": "^6.0.0",
101
101
  "split2": "^4.2.0",
102
102
  "toobusy-js": "^0.5.1",
103
- "undici": "^6.12.0",
103
+ "undici": "^6.13.0",
104
104
  "url-join": "^5.0.0"
105
105
  },
106
106
  "devDependencies": {
@@ -111,7 +111,7 @@
111
111
  "eslint-config-prettier": "^9.1.0",
112
112
  "eslint-config-standard": "^17.0.0",
113
113
  "eslint-plugin-import": "^2.29.1",
114
- "eslint-plugin-n": "^17.1.0",
114
+ "eslint-plugin-n": "^17.2.1",
115
115
  "eslint-plugin-node": "^11.1.0",
116
116
  "eslint-plugin-promise": "^6.0.0",
117
117
  "husky": "^9.0.11",