@nxtedition/lib 19.0.51 → 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 +383 -89
  2. package/package.json +7 -7
package/couch.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import assert from 'node:assert'
2
+ import stream from 'node:stream'
3
+ import querystring from 'node:querystring'
2
4
  import createError from 'http-errors'
3
5
  import { makeWeakCache } from './weakCache.js'
4
6
  import tp from 'timers/promises'
5
7
  import { defaultDelay as delay } from './http.js'
6
- import querystring from 'querystring'
7
8
  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).
@@ -560,93 +560,7 @@ export function makeCouch(opts) {
560
560
 
561
561
  const { client = getClient(pathname), signal, idempotent = true, ...options } = opts
562
562
 
563
- const params = {}
564
- const headers = ['Accept', 'application/json']
565
-
566
- let method = 'GET'
567
- let body
568
-
569
- if (options.conflicts) {
570
- params.conflicts = true
571
- }
572
-
573
- if (options.update_seq) {
574
- params.update_seq = true
575
- }
576
-
577
- if (options.descending) {
578
- params.descending = true
579
- }
580
-
581
- if (options.include_docs) {
582
- params.include_docs = true
583
- }
584
-
585
- if (options.include_docs) {
586
- params.include_docs = true
587
- }
588
-
589
- if (options.sorted) {
590
- params.sorted = true
591
- }
592
-
593
- if (options.key != null) {
594
- params.key = JSON.stringify(options.key)
595
- }
596
-
597
- if (options.start_key != null) {
598
- options.startkey = options.start_key
599
- }
600
-
601
- if (options.startkey != null) {
602
- params.startkey = JSON.stringify(options.startkey)
603
- }
604
-
605
- if (options.startkey_docid != null) {
606
- params.startkey_docid = JSON.stringify(options.startkey_docid)
607
- }
608
-
609
- if (options.end_key != null) {
610
- options.endkey = options.end_key
611
- }
612
-
613
- if (options.endkey != null) {
614
- params.endkey = JSON.stringify(options.endkey)
615
- }
616
-
617
- if (options.inclusive_end != null) {
618
- params.inclusive_end = !!options.inclusive_end
619
- }
620
-
621
- if (options.limit != null) {
622
- params.limit = options.limit
623
- }
624
-
625
- if (options.skip != null) {
626
- params.skip = options.skip
627
- }
628
-
629
- if (options.stale != null) {
630
- params.stale = options.stale
631
- }
632
-
633
- if (options.reduce != null) {
634
- params.reduce = options.reduce
635
- }
636
-
637
- if (options.group != null) {
638
- params.group = options.group
639
- }
640
-
641
- if (options.group_level != null) {
642
- params.group_level = options.group_level
643
- }
644
-
645
- if (options.keys != null) {
646
- method = 'POST'
647
- body = { keys: options.keys }
648
- headers.push('Content-Type', 'application/json')
649
- }
563
+ const { params, method, headers, body } = _normalizeAllDocsOptions(options)
650
564
 
651
565
  const req = {
652
566
  pathname: pathname || '_all_docs',
@@ -848,3 +762,383 @@ export function makeCouch(opts) {
848
762
  up,
849
763
  })
850
764
  }
765
+
766
+ /* eslint-disable camelcase */
767
+ function _normalizeAllDocsOptions({
768
+ conflicts,
769
+ update_seq,
770
+ descending,
771
+ include_docs,
772
+ sorted,
773
+ key,
774
+ startkey,
775
+ start_key,
776
+ startkey_docid,
777
+ endkey,
778
+ end_key,
779
+ endkey_docid,
780
+ inclusive_end,
781
+ limit,
782
+ skip,
783
+ stale,
784
+ reduce,
785
+ stable,
786
+ update,
787
+ keys,
788
+ group,
789
+ group_level,
790
+ ...rest
791
+ }) {
792
+ if (Object.keys(rest).length > 0) {
793
+ throw new Error('invalid options: ' + JSON.stringify(rest))
794
+ }
795
+ const params = {}
796
+ const headers = {
797
+ Accept: 'application/json',
798
+ }
799
+
800
+ let method = 'GET'
801
+ let body
802
+
803
+ if (conflicts) {
804
+ params.conflicts = true
805
+ }
806
+
807
+ if (update_seq) {
808
+ params.update_seq = true
809
+ }
810
+
811
+ if (descending) {
812
+ params.descending = true
813
+ }
814
+
815
+ if (include_docs) {
816
+ params.include_docs = true
817
+ }
818
+
819
+ if (sorted) {
820
+ params.sorted = true
821
+ }
822
+
823
+ if (key != null) {
824
+ params.key = JSON.stringify(key)
825
+ }
826
+
827
+ if (startkey != null || start_key != null) {
828
+ params.startkey = JSON.stringify(startkey ?? start_key)
829
+ }
830
+
831
+ if (startkey_docid != null) {
832
+ params.startkey_docid = JSON.stringify(startkey_docid)
833
+ }
834
+
835
+ if (endkey != null || end_key != null) {
836
+ params.endkey = JSON.stringify(endkey ?? end_key)
837
+ }
838
+
839
+ if (inclusive_end != null) {
840
+ params.inclusive_end = !!inclusive_end
841
+ }
842
+
843
+ if (limit != null) {
844
+ params.limit = limit
845
+ }
846
+
847
+ if (skip != null) {
848
+ params.skip = skip
849
+ }
850
+
851
+ if (stale != null) {
852
+ params.stale = stale
853
+ }
854
+
855
+ if (reduce != null) {
856
+ params.reduce = reduce
857
+ }
858
+
859
+ if (group != null) {
860
+ params.group = group
861
+ }
862
+
863
+ if (group_level != null) {
864
+ params.group_level = group_level
865
+ }
866
+
867
+ if (update != null) {
868
+ params.update = update
869
+ }
870
+
871
+ if (stable != null) {
872
+ params.stable = stable
873
+ }
874
+
875
+ if (keys != null) {
876
+ method = 'POST'
877
+ body = JSON.stringify({ keys })
878
+ headers['Content-Type'] = 'application/json'
879
+ }
880
+
881
+ return {
882
+ method,
883
+ params,
884
+ headers,
885
+ body,
886
+ }
887
+ }
888
+
889
+ class Handler extends undici.DecoratorHandler {
890
+ #str
891
+ #decoder
892
+ #onAbort
893
+ #signal
894
+ #statusCode
895
+ #statusText
896
+ #opts
897
+ #handler
898
+
899
+ constructor({ opts, signal, handler }) {
900
+ super(handler)
901
+
902
+ this.#handler = handler
903
+ this.#opts = opts
904
+ this.#signal = signal
905
+ }
906
+
907
+ onConnect(abort) {
908
+ if (this.#signal?.aborted) {
909
+ abort(this.#signal.reason)
910
+ } else {
911
+ if (this.#signal) {
912
+ this.#onAbort = () => abort(this.#signal.reason)
913
+ this.#signal.addEventListener('abort', this.#onAbort)
914
+ }
915
+
916
+ this.#str = ''
917
+ this.#decoder = null
918
+ this.#handler.onConnect?.(abort)
919
+ }
920
+ }
921
+
922
+ onHeaders(statusCode, rawHeaders, resume, statusText) {
923
+ this.#statusCode = statusCode
924
+ this.#statusText = statusText
925
+
926
+ if (this.#statusCode >= 200 && this.#statusCode < 300) {
927
+ this.#handler.onHeaders?.(statusCode, rawHeaders, resume, statusText)
928
+ } else {
929
+ this.#decoder = new TextDecoder()
930
+ }
931
+ }
932
+
933
+ onData(data) {
934
+ if (this.#decoder) {
935
+ this.#str += this.#decoder.decode(data, { stream: true })
936
+ } else {
937
+ return this.#handler?.onData(data)
938
+ }
939
+ }
940
+
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
+ }
953
+
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,
963
+ },
964
+ },
965
+ }),
966
+ )
967
+ } else {
968
+ this.onFinally(null, trailers)
969
+ }
970
+ }
971
+
972
+ onError(err) {
973
+ this.onFinally(err)
974
+ }
975
+
976
+ onFinally(err, val) {
977
+ if (this.#signal) {
978
+ this.#signal.removeEventListener('abort', this.#onAbort)
979
+ }
980
+
981
+ if (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()
1018
+ } else {
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
+ }
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)
1063
+ }
1064
+ }
1065
+
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(
1103
+ url,
1104
+ {
1105
+ body,
1106
+ method = body ? 'POST' : 'GET',
1107
+ query,
1108
+ headers,
1109
+ dispatcher = undici.getGlobalDispatcher(),
1110
+ signal,
1111
+ stream,
1112
+ },
1113
+ ) {
1114
+ url = new URL(url)
1115
+ if (url.search) {
1116
+ throw new Error('invalid url: ' + url.href)
1117
+ }
1118
+ const opts = {
1119
+ origin: url.origin,
1120
+ path: url.pathname + '?' + querystring.stringify(query),
1121
+ method,
1122
+ headers: {
1123
+ 'content-type': body != null && typeof body === 'object' ? 'application/json' : 'plain/text',
1124
+ accept: 'application/json',
1125
+ ...headers,
1126
+ },
1127
+ body: body != null && typeof body === 'object' ? JSON.stringify(body) : body,
1128
+ }
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
+ }
1144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "19.0.51",
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",