@nxtedition/lib 19.1.1 → 19.1.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.
- package/couch.js +111 -60
- package/package.json +2 -1
- package/s3.js +18 -11
package/couch.js
CHANGED
|
@@ -6,9 +6,10 @@ import { makeWeakCache } from './weakCache.js'
|
|
|
6
6
|
import tp from 'timers/promises'
|
|
7
7
|
import { defaultDelay as delay } from './http.js'
|
|
8
8
|
import urljoin from 'url-join'
|
|
9
|
-
import undici from 'undici'
|
|
9
|
+
import undici, { util as undiciUtil } from 'undici'
|
|
10
10
|
import { AbortError } from './errors.js'
|
|
11
11
|
import split2 from 'split2'
|
|
12
|
+
import { parse as parseContentType } from 'content-type'
|
|
12
13
|
|
|
13
14
|
// https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
|
|
14
15
|
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
@@ -886,22 +887,15 @@ function _normalizeAllDocsOptions({
|
|
|
886
887
|
}
|
|
887
888
|
}
|
|
888
889
|
|
|
889
|
-
class
|
|
890
|
-
#str
|
|
891
|
-
#decoder
|
|
892
|
-
#onAbort
|
|
890
|
+
class SignalHandler extends undici.DecoratorHandler {
|
|
893
891
|
#signal
|
|
894
|
-
#statusCode
|
|
895
|
-
#statusText
|
|
896
|
-
#opts
|
|
897
892
|
#handler
|
|
893
|
+
#abort
|
|
898
894
|
|
|
899
|
-
constructor({
|
|
895
|
+
constructor({ signal, handler }) {
|
|
900
896
|
super(handler)
|
|
901
|
-
|
|
902
|
-
this.#handler = handler
|
|
903
|
-
this.#opts = opts
|
|
904
897
|
this.#signal = signal
|
|
898
|
+
this.#handler = handler
|
|
905
899
|
}
|
|
906
900
|
|
|
907
901
|
onConnect(abort) {
|
|
@@ -909,50 +903,94 @@ class Handler extends undici.DecoratorHandler {
|
|
|
909
903
|
abort(this.#signal.reason)
|
|
910
904
|
} else {
|
|
911
905
|
if (this.#signal) {
|
|
912
|
-
this.#
|
|
913
|
-
this.#signal.addEventListener('abort', this.#
|
|
906
|
+
this.#abort = () => abort(this.#signal.reason)
|
|
907
|
+
this.#signal.addEventListener('abort', this.#abort)
|
|
914
908
|
}
|
|
915
|
-
|
|
916
|
-
this.#str = ''
|
|
917
|
-
this.#decoder = null
|
|
918
909
|
this.#handler.onConnect?.(abort)
|
|
919
910
|
}
|
|
920
911
|
}
|
|
921
912
|
|
|
922
|
-
|
|
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
|
+
#statusText
|
|
929
|
+
#contentType
|
|
930
|
+
#opts
|
|
931
|
+
#handler
|
|
932
|
+
#error
|
|
933
|
+
#headers
|
|
934
|
+
|
|
935
|
+
constructor({ opts, handler }) {
|
|
936
|
+
super(handler)
|
|
937
|
+
|
|
938
|
+
this.#handler = handler
|
|
939
|
+
this.#opts = opts
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
onConnect(...args) {
|
|
943
|
+
this.#str = ''
|
|
944
|
+
this.#decoder = null
|
|
945
|
+
this.#handler.onConnect?.(...args)
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
onHeaders(statusCode, headers, resume, statusText) {
|
|
923
949
|
this.#statusCode = statusCode
|
|
924
950
|
this.#statusText = statusText
|
|
925
951
|
|
|
926
|
-
if (this.#statusCode
|
|
927
|
-
this.#
|
|
928
|
-
|
|
952
|
+
if (this.#statusCode < 200 || this.#statusCode > 300) {
|
|
953
|
+
this.#headers = Array.isArray(headers) ? undiciUtil.parseHeaders(headers) : headers
|
|
954
|
+
|
|
955
|
+
this.#error = new Error(this.#statusText ?? this.#statusCode)
|
|
956
|
+
|
|
957
|
+
this.#contentType = parseContentType(this.#headers['content-type'] ?? '')?.type
|
|
958
|
+
if (this.#contentType !== 'application/json' && this.#contentType !== 'plain/text') {
|
|
959
|
+
throw this.#error
|
|
960
|
+
}
|
|
961
|
+
|
|
929
962
|
this.#decoder = new TextDecoder()
|
|
963
|
+
} else {
|
|
964
|
+
return this.#handler.onHeaders?.(statusCode, headers, resume, statusText)
|
|
930
965
|
}
|
|
931
966
|
}
|
|
932
967
|
|
|
933
968
|
onData(data) {
|
|
934
|
-
if (this.#
|
|
935
|
-
this.#str += this.#decoder
|
|
969
|
+
if (this.#error) {
|
|
970
|
+
this.#str += this.#decoder?.decode(data, { stream: true }) ?? ''
|
|
936
971
|
} else {
|
|
937
972
|
return this.#handler?.onData(data)
|
|
938
973
|
}
|
|
939
974
|
}
|
|
940
975
|
|
|
941
|
-
onComplete(
|
|
942
|
-
if (this.#
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
976
|
+
onComplete(...args) {
|
|
977
|
+
if (this.#error) {
|
|
978
|
+
this.#str += this.#decoder?.decode(undefined, { stream: false }) ?? ''
|
|
979
|
+
|
|
980
|
+
if (this.#contentType === 'application/json') {
|
|
981
|
+
let body
|
|
982
|
+
let reason
|
|
983
|
+
let error
|
|
984
|
+
try {
|
|
985
|
+
body = JSON.parse(this.#str)
|
|
986
|
+
reason = body.reason
|
|
987
|
+
error = body.error
|
|
988
|
+
} catch {
|
|
989
|
+
body = this.#str
|
|
990
|
+
// Do nothing...
|
|
991
|
+
}
|
|
953
992
|
|
|
954
|
-
|
|
955
|
-
Object.assign(new Error(this.#statusText ?? reason), {
|
|
993
|
+
throw Object.assign(this.#error, {
|
|
956
994
|
reason,
|
|
957
995
|
error,
|
|
958
996
|
data: {
|
|
@@ -962,26 +1000,23 @@ class Handler extends undici.DecoratorHandler {
|
|
|
962
1000
|
statusCode: this.#statusCode,
|
|
963
1001
|
},
|
|
964
1002
|
},
|
|
965
|
-
})
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
this.#signal.removeEventListener('abort', this.#onAbort)
|
|
979
|
-
}
|
|
1003
|
+
})
|
|
1004
|
+
} else if (this.#contentType === 'plain/text') {
|
|
1005
|
+
throw Object.assign(this.#error, {
|
|
1006
|
+
data: {
|
|
1007
|
+
ureq: this.#opts,
|
|
1008
|
+
ures: {
|
|
1009
|
+
body: this.#str,
|
|
1010
|
+
statusCode: this.#statusCode,
|
|
1011
|
+
headers: this.#headers,
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
})
|
|
1015
|
+
}
|
|
980
1016
|
|
|
981
|
-
|
|
982
|
-
this.#handler.onError?.(err)
|
|
1017
|
+
assert(false)
|
|
983
1018
|
} else {
|
|
984
|
-
this.#handler.onComplete?.(
|
|
1019
|
+
this.#handler.onComplete?.(...args)
|
|
985
1020
|
}
|
|
986
1021
|
}
|
|
987
1022
|
}
|
|
@@ -992,10 +1027,12 @@ class StreamOutput extends stream.Readable {
|
|
|
992
1027
|
#abort
|
|
993
1028
|
#state
|
|
994
1029
|
#decoder
|
|
1030
|
+
#batched
|
|
995
1031
|
#didPush = false
|
|
996
1032
|
|
|
997
|
-
constructor({ highWaterMark }) {
|
|
1033
|
+
constructor({ batched = false, highWaterMark = batched ? 2 : 128 }) {
|
|
998
1034
|
super({ objectMode: true, highWaterMark })
|
|
1035
|
+
this.#batched = batched
|
|
999
1036
|
}
|
|
1000
1037
|
|
|
1001
1038
|
_read() {
|
|
@@ -1033,6 +1070,7 @@ class StreamOutput extends stream.Readable {
|
|
|
1033
1070
|
lines[0] = this.#str + lines[0]
|
|
1034
1071
|
this.#str = lines.pop() ?? ''
|
|
1035
1072
|
|
|
1073
|
+
const rows = this.#batched ? [] : null
|
|
1036
1074
|
for (const line of lines) {
|
|
1037
1075
|
if (this.#state === 0) {
|
|
1038
1076
|
if (line.endsWith('[')) {
|
|
@@ -1044,12 +1082,22 @@ class StreamOutput extends stream.Readable {
|
|
|
1044
1082
|
if (line.startsWith(']')) {
|
|
1045
1083
|
this.#state = 2
|
|
1046
1084
|
} else {
|
|
1047
|
-
|
|
1048
|
-
|
|
1085
|
+
const row = JSON.parse(line.slice(0, line.lastIndexOf('}') + 1))
|
|
1086
|
+
if (rows) {
|
|
1087
|
+
rows.push(row)
|
|
1088
|
+
} else {
|
|
1089
|
+
this.push(row)
|
|
1090
|
+
this.#didPush = true
|
|
1091
|
+
}
|
|
1049
1092
|
}
|
|
1050
1093
|
}
|
|
1051
1094
|
}
|
|
1052
1095
|
|
|
1096
|
+
if (rows) {
|
|
1097
|
+
this.push(rows)
|
|
1098
|
+
this.#didPush = true
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1053
1101
|
return this.readableLength < this.readableHighWaterMark
|
|
1054
1102
|
}
|
|
1055
1103
|
|
|
@@ -1121,6 +1169,8 @@ export function request(
|
|
|
1121
1169
|
method,
|
|
1122
1170
|
headers: {
|
|
1123
1171
|
'content-type': body != null && typeof body === 'object' ? 'application/json' : 'plain/text',
|
|
1172
|
+
'user-agent': globalThis.userAgent,
|
|
1173
|
+
'request-id': genReqId(),
|
|
1124
1174
|
accept: 'application/json',
|
|
1125
1175
|
...headers,
|
|
1126
1176
|
},
|
|
@@ -1128,11 +1178,12 @@ export function request(
|
|
|
1128
1178
|
}
|
|
1129
1179
|
|
|
1130
1180
|
dispatcher = dispatcher.compose(
|
|
1131
|
-
(dispatch) => (opts, handler) => dispatch(opts, new
|
|
1181
|
+
(dispatch) => (opts, handler) => dispatch(opts, new ErrorHandler({ opts, handler })),
|
|
1182
|
+
(dispatch) => (opts, handler) => dispatch(opts, new SignalHandler({ signal, handler })),
|
|
1132
1183
|
)
|
|
1133
1184
|
|
|
1134
1185
|
if (stream) {
|
|
1135
|
-
const handler = new StreamOutput(
|
|
1186
|
+
const handler = new StreamOutput(stream)
|
|
1136
1187
|
dispatcher.dispatch(opts, handler)
|
|
1137
1188
|
return handler
|
|
1138
1189
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/lib",
|
|
3
|
-
"version": "19.1.
|
|
3
|
+
"version": "19.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Robert Nagy <robert.nagy@boffins.se>",
|
|
6
6
|
"type": "module",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"@elastic/elasticsearch": "^8.13.1",
|
|
80
80
|
"@elastic/transport": "^8.5.1",
|
|
81
81
|
"@nxtedition/nxt-undici": "^2.0.46",
|
|
82
|
+
"content-type": "^1.0.5",
|
|
82
83
|
"date-fns": "^3.6.0",
|
|
83
84
|
"fast-querystring": "^1.1.1",
|
|
84
85
|
"hasha": "^6.0.0",
|
package/s3.js
CHANGED
|
@@ -4,11 +4,24 @@ import tp from 'node:timers/promises'
|
|
|
4
4
|
import AWS from '@aws-sdk/client-s3'
|
|
5
5
|
import PQueue from 'p-queue'
|
|
6
6
|
|
|
7
|
-
const CONTENT_MD5_EXPR = /^[A-F0-9]{32}$/i
|
|
8
|
-
const CONTENT_LENGTH_EXPR = /^\d+$/i
|
|
9
|
-
|
|
10
7
|
const queue = new PQueue({ concurrency: 8 })
|
|
11
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Uploads a file to S3 using multipart upload.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} options - The options for the upload.
|
|
13
|
+
* @param {AWS.S3} options.client - The S3 client.
|
|
14
|
+
* @param {AbortSignal} options.signal - The signal to abort the upload.
|
|
15
|
+
* @param {Object} options.logger - The logger to use.
|
|
16
|
+
* @param {number} [options.partSize=16e6] - The size of each part in the multipart upload.
|
|
17
|
+
* @param {Object} options.params - The parameters for the upload.
|
|
18
|
+
* @param {Buffer|NodeJS.ReadStream} options.params.Body - The data to upload.
|
|
19
|
+
* @param {string} options.params.Key - The key of the object.
|
|
20
|
+
* @param {string} options.params.Bucket - The name of the bucket.
|
|
21
|
+
* @param {string} [options.params.ContentMD5] - The MD5 hash of the object as base64 string.
|
|
22
|
+
* @param {number} [options.params.ContentLength] - The length of the object.
|
|
23
|
+
* @returns {Promise<Object>} The result of the upload.
|
|
24
|
+
*/
|
|
12
25
|
export async function upload({ client: s3, signal, logger, partSize = 16e6, params }) {
|
|
13
26
|
if (s3 == null) {
|
|
14
27
|
throw new Error('Invalid client')
|
|
@@ -24,13 +37,7 @@ export async function upload({ client: s3, signal, logger, partSize = 16e6, para
|
|
|
24
37
|
|
|
25
38
|
const { Body, Key, Bucket, ContentMD5, ContentLength } = params ?? {}
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
throw new Error(`Invalid ContentMD5: ${ContentMD5}`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (ContentLength != null && !CONTENT_LENGTH_EXPR.test(ContentLength)) {
|
|
32
|
-
throw new Error(`Invalid ContentLength: ${ContentLength}`)
|
|
33
|
-
}
|
|
40
|
+
// TODO (fix): Valdate ContentMD & ContentLength
|
|
34
41
|
|
|
35
42
|
const promises = []
|
|
36
43
|
|
|
@@ -190,7 +197,7 @@ export async function upload({ client: s3, signal, logger, partSize = 16e6, para
|
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
const size = ContentLength != null ? Number(ContentLength) : null
|
|
193
|
-
const hash = ContentMD5
|
|
200
|
+
const hash = ContentMD5 ? Buffer.from(ContentMD5, 'base64').toString('hex') : null
|
|
194
201
|
|
|
195
202
|
if (size != null && size !== result.size) {
|
|
196
203
|
throw new Error(`Expected size ${size} but got ${result.size}`)
|