@nxtedition/lib 19.1.2 → 19.1.5

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 (4) hide show
  1. package/couch.js +6 -4
  2. package/errors.js +14 -4
  3. package/package.json +1 -1
  4. package/s3.js +37 -15
package/couch.js CHANGED
@@ -930,6 +930,7 @@ class ErrorHandler extends undici.DecoratorHandler {
930
930
  #opts
931
931
  #handler
932
932
  #error
933
+ #headers
933
934
 
934
935
  constructor({ opts, handler }) {
935
936
  super(handler)
@@ -949,11 +950,11 @@ class ErrorHandler extends undici.DecoratorHandler {
949
950
  this.#statusText = statusText
950
951
 
951
952
  if (this.#statusCode < 200 || this.#statusCode > 300) {
952
- headers = Array.isArray(headers) ? undiciUtil.parseHeaders(headers) : headers
953
+ this.#headers = Array.isArray(headers) ? undiciUtil.parseHeaders(headers) : headers
953
954
 
954
955
  this.#error = new Error(this.#statusText ?? this.#statusCode)
955
956
 
956
- this.#contentType = parseContentType(headers['content-type'] ?? '')?.type
957
+ this.#contentType = parseContentType(this.#headers['content-type'] ?? '')?.type
957
958
  if (this.#contentType !== 'application/json' && this.#contentType !== 'plain/text') {
958
959
  throw this.#error
959
960
  }
@@ -1003,10 +1004,11 @@ class ErrorHandler extends undici.DecoratorHandler {
1003
1004
  } else if (this.#contentType === 'plain/text') {
1004
1005
  throw Object.assign(this.#error, {
1005
1006
  data: {
1006
- req: this.#opts,
1007
- res: {
1007
+ ureq: this.#opts,
1008
+ ures: {
1008
1009
  body: this.#str,
1009
1010
  statusCode: this.#statusCode,
1011
+ headers: this.#headers,
1010
1012
  },
1011
1013
  },
1012
1014
  })
package/errors.js CHANGED
@@ -42,12 +42,16 @@ export function parseError(error) {
42
42
  const kSeen = Symbol('kSeen')
43
43
 
44
44
  export function serializeError(error) {
45
+ return _serializeError(error, { depth: 0 })
46
+ }
47
+
48
+ function _serializeError(error, { depth }) {
45
49
  if (!error) {
46
50
  return null
47
51
  }
48
52
 
49
53
  if (typeof error === 'string') {
50
- return [serializeError({ message: error })]
54
+ return [_serializeError({ message: error }, { depth })]
51
55
  }
52
56
 
53
57
  if (Buffer.isBuffer(error)) {
@@ -55,13 +59,17 @@ export function serializeError(error) {
55
59
  }
56
60
 
57
61
  if (Array.isArray(error)) {
58
- return error.map(serializeError).filter(Boolean)
62
+ return error.map((x) => _serializeError(x, { depth })).filter(Boolean)
59
63
  }
60
64
 
61
65
  if (Object.prototype.hasOwnProperty.call(error, kSeen)) {
62
66
  return null
63
67
  }
64
68
 
69
+ if (depth > 8) {
70
+ return [{ messages: 'Maximum error serialization depth exceeded' }]
71
+ }
72
+
65
73
  error[kSeen] = undefined
66
74
 
67
75
  const type =
@@ -104,8 +112,10 @@ export function serializeError(error) {
104
112
  signalCode = SIGNALS[signalCode] ?? signalCode
105
113
  }
106
114
 
107
- errors = Array.isArray(errors) ? errors.map(serializeError).filter(Boolean) : undefined
108
- cause = cause ? serializeError(cause) : undefined
115
+ errors = Array.isArray(errors)
116
+ ? errors.map((x) => _serializeError(x, { depth: depth + 1 })).filter(Boolean)
117
+ : undefined
118
+ cause = cause ? _serializeError(cause, { depth: depth + 1 }) : undefined
109
119
 
110
120
  delete error[kSeen]
111
121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "19.1.2",
3
+ "version": "19.1.5",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
package/s3.js CHANGED
@@ -4,12 +4,34 @@ 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
- const queue = new PQueue({ concurrency: 8 })
11
-
12
- export async function upload({ client: s3, signal, logger, partSize = 16e6, params }) {
7
+ const QUEUE = new PQueue({ concurrency: 8 })
8
+ const MD5_HEX_EXPR = /^[A-F0-9]{32}$/i
9
+
10
+ /**
11
+ * Uploads a file to S3 using multipart upload.
12
+ *
13
+ * @param {Object} options - The options for the upload.
14
+ * @param {AWS.S3} options.client - The S3 client.
15
+ * @param {AbortSignal} options.signal - The signal to abort the upload.
16
+ * @param {Object} options.logger - The logger to use.
17
+ * @param {number} [options.partSize=16e6] - The size of each part in the multipart upload.
18
+ * @param {PQueue} [options.queue] - The queue to use for part uploads.
19
+ * @param {Object} options.params - The parameters for the upload.
20
+ * @param {Buffer|NodeJS.ReadStream} options.params.Body - The data to upload.
21
+ * @param {string} options.params.Key - The key of the object.
22
+ * @param {string} options.params.Bucket - The name of the bucket.
23
+ * @param {string} [options.params.ContentMD5] - The MD5 hash of the object as base64 string.
24
+ * @param {number} [options.params.ContentLength] - The length of the object.
25
+ * @returns {Promise<Object>} The result of the upload.
26
+ */
27
+ export async function upload({
28
+ client: s3,
29
+ queue = QUEUE,
30
+ signal,
31
+ logger,
32
+ partSize = 16e6,
33
+ params,
34
+ }) {
13
35
  if (s3 == null) {
14
36
  throw new Error('Invalid client')
15
37
  }
@@ -22,14 +44,17 @@ export async function upload({ client: s3, signal, logger, partSize = 16e6, para
22
44
  throw new Error('Invalid params')
23
45
  }
24
46
 
25
- const { Body, Key, Bucket, ContentMD5, ContentLength } = params ?? {}
47
+ const { Body, Key, Bucket, ContentMD5, ContentLength } = params
26
48
 
27
- if (ContentMD5 != null && !CONTENT_MD5_EXPR.test(ContentMD5)) {
28
- throw new Error(`Invalid ContentMD5: ${ContentMD5}`)
49
+ const size = ContentLength != null ? Number(ContentLength) : null
50
+
51
+ if (size != null && (!Number.isFinite(size) || size < 0)) {
52
+ throw new Error('Invalid params.ContentLength')
29
53
  }
30
54
 
31
- if (ContentLength != null && !CONTENT_LENGTH_EXPR.test(ContentLength)) {
32
- throw new Error(`Invalid ContentLength: ${ContentLength}`)
55
+ const hash = ContentMD5 != null ? Buffer.from(ContentMD5, 'base64').toString('hex') : null
56
+ if (hash != null && !MD5_HEX_EXPR.test(hash)) {
57
+ throw new Error('Invalid params.ContentMD5')
33
58
  }
34
59
 
35
60
  const promises = []
@@ -116,7 +141,7 @@ export async function upload({ client: s3, signal, logger, partSize = 16e6, para
116
141
  } catch (err) {
117
142
  logger?.warn({ err }, 'part upload failed')
118
143
 
119
- if (retryCount < 3) {
144
+ if (retryCount < 5) {
120
145
  logger?.debug({ retryCount }, 'part upload retry')
121
146
  await tp.setTimeout(1e3, undefined, { signal: uploader.signal })
122
147
  } else {
@@ -189,9 +214,6 @@ export async function upload({ client: s3, signal, logger, partSize = 16e6, para
189
214
  parts,
190
215
  }
191
216
 
192
- const size = ContentLength != null ? Number(ContentLength) : null
193
- const hash = ContentMD5
194
-
195
217
  if (size != null && size !== result.size) {
196
218
  throw new Error(`Expected size ${size} but got ${result.size}`)
197
219
  }