@nxtedition/nxt-undici 2.0.20 → 2.0.21

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/lib/errors.js ADDED
@@ -0,0 +1,214 @@
1
+ export class UndiciError extends Error {
2
+ constructor(message) {
3
+ super(message)
4
+ this.name = 'UndiciError'
5
+ this.code = 'UND_ERR'
6
+ }
7
+ }
8
+
9
+ export class ConnectTimeoutError extends UndiciError {
10
+ constructor(message) {
11
+ super(message)
12
+ Error.captureStackTrace(this, ConnectTimeoutError)
13
+ this.name = 'ConnectTimeoutError'
14
+ this.message = message || 'Connect Timeout Error'
15
+ this.code = 'UND_ERR_CONNECT_TIMEOUT'
16
+ }
17
+ }
18
+
19
+ export class HeadersTimeoutError extends UndiciError {
20
+ constructor(message) {
21
+ super(message)
22
+ Error.captureStackTrace(this, HeadersTimeoutError)
23
+ this.name = 'HeadersTimeoutError'
24
+ this.message = message || 'Headers Timeout Error'
25
+ this.code = 'UND_ERR_HEADERS_TIMEOUT'
26
+ }
27
+ }
28
+
29
+ export class HeadersOverflowError extends UndiciError {
30
+ constructor(message) {
31
+ super(message)
32
+ Error.captureStackTrace(this, HeadersOverflowError)
33
+ this.name = 'HeadersOverflowError'
34
+ this.message = message || 'Headers Overflow Error'
35
+ this.code = 'UND_ERR_HEADERS_OVERFLOW'
36
+ }
37
+ }
38
+
39
+ export class BodyTimeoutError extends UndiciError {
40
+ constructor(message) {
41
+ super(message)
42
+ Error.captureStackTrace(this, BodyTimeoutError)
43
+ this.name = 'BodyTimeoutError'
44
+ this.message = message || 'Body Timeout Error'
45
+ this.code = 'UND_ERR_BODY_TIMEOUT'
46
+ }
47
+ }
48
+
49
+ export class ResponseStatusCodeError extends UndiciError {
50
+ constructor(message, statusCode, headers, body) {
51
+ super(message)
52
+ Error.captureStackTrace(this, ResponseStatusCodeError)
53
+ this.name = 'ResponseStatusCodeError'
54
+ this.message = message || 'Response Status Code Error'
55
+ this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
56
+ this.body = body
57
+ this.status = statusCode
58
+ this.statusCode = statusCode
59
+ this.headers = headers
60
+ }
61
+ }
62
+
63
+ export class InvalidArgumentError extends UndiciError {
64
+ constructor(message) {
65
+ super(message)
66
+ Error.captureStackTrace(this, InvalidArgumentError)
67
+ this.name = 'InvalidArgumentError'
68
+ this.message = message || 'Invalid Argument Error'
69
+ this.code = 'UND_ERR_INVALID_ARG'
70
+ }
71
+ }
72
+
73
+ export class InvalidReturnValueError extends UndiciError {
74
+ constructor(message) {
75
+ super(message)
76
+ Error.captureStackTrace(this, InvalidReturnValueError)
77
+ this.name = 'InvalidReturnValueError'
78
+ this.message = message || 'Invalid Return Value Error'
79
+ this.code = 'UND_ERR_INVALID_RETURN_VALUE'
80
+ }
81
+ }
82
+
83
+ export class AbortError extends UndiciError {
84
+ constructor(message) {
85
+ super(message)
86
+ Error.captureStackTrace(this, RequestAbortedError)
87
+ this.name = 'AbortError'
88
+ this.message = message || 'The operation was aborted'
89
+ }
90
+ }
91
+
92
+ export class RequestAbortedError extends AbortError {
93
+ constructor(message) {
94
+ super(message)
95
+ Error.captureStackTrace(this, RequestAbortedError)
96
+ this.name = 'AbortError'
97
+ this.message = message || 'Request aborted'
98
+ this.code = 'UND_ERR_ABORTED'
99
+ }
100
+ }
101
+
102
+ export class InformationalError extends UndiciError {
103
+ constructor(message) {
104
+ super(message)
105
+ Error.captureStackTrace(this, InformationalError)
106
+ this.name = 'InformationalError'
107
+ this.message = message || 'Request information'
108
+ this.code = 'UND_ERR_INFO'
109
+ }
110
+ }
111
+
112
+ export class RequestContentLengthMismatchError extends UndiciError {
113
+ constructor(message) {
114
+ super(message)
115
+ Error.captureStackTrace(this, RequestContentLengthMismatchError)
116
+ this.name = 'RequestContentLengthMismatchError'
117
+ this.message = message || 'Request body length does not match content-length header'
118
+ this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
119
+ }
120
+ }
121
+
122
+ export class ResponseContentLengthMismatchError extends UndiciError {
123
+ constructor(message) {
124
+ super(message)
125
+ Error.captureStackTrace(this, ResponseContentLengthMismatchError)
126
+ this.name = 'ResponseContentLengthMismatchError'
127
+ this.message = message || 'Response body length does not match content-length header'
128
+ this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
129
+ }
130
+ }
131
+
132
+ export class ClientDestroyedError extends UndiciError {
133
+ constructor(message) {
134
+ super(message)
135
+ Error.captureStackTrace(this, ClientDestroyedError)
136
+ this.name = 'ClientDestroyedError'
137
+ this.message = message || 'The client is destroyed'
138
+ this.code = 'UND_ERR_DESTROYED'
139
+ }
140
+ }
141
+
142
+ export class ClientClosedError extends UndiciError {
143
+ constructor(message) {
144
+ super(message)
145
+ Error.captureStackTrace(this, ClientClosedError)
146
+ this.name = 'ClientClosedError'
147
+ this.message = message || 'The client is closed'
148
+ this.code = 'UND_ERR_CLOSED'
149
+ }
150
+ }
151
+
152
+ export class SocketError extends UndiciError {
153
+ constructor(message, socket) {
154
+ super(message)
155
+ Error.captureStackTrace(this, SocketError)
156
+ this.name = 'SocketError'
157
+ this.message = message || 'Socket error'
158
+ this.code = 'UND_ERR_SOCKET'
159
+ this.socket = socket
160
+ }
161
+ }
162
+
163
+ export class NotSupportedError extends UndiciError {
164
+ constructor(message) {
165
+ super(message)
166
+ Error.captureStackTrace(this, NotSupportedError)
167
+ this.name = 'NotSupportedError'
168
+ this.message = message || 'Not supported error'
169
+ this.code = 'UND_ERR_NOT_SUPPORTED'
170
+ }
171
+ }
172
+
173
+ export class BalancedPoolMissingUpstreamError extends UndiciError {
174
+ constructor(message) {
175
+ super(message)
176
+ Error.captureStackTrace(this, NotSupportedError)
177
+ this.name = 'MissingUpstreamError'
178
+ this.message = message || 'No upstream has been added to the BalancedPool'
179
+ this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
180
+ }
181
+ }
182
+
183
+ export class HTTPParserError extends Error {
184
+ constructor(message, code, data) {
185
+ super(message)
186
+ Error.captureStackTrace(this, HTTPParserError)
187
+ this.name = 'HTTPParserError'
188
+ this.code = code ? `HPE_${code}` : undefined
189
+ this.data = data ? data.toString() : undefined
190
+ }
191
+ }
192
+
193
+ export class ResponseExceededMaxSizeError extends UndiciError {
194
+ constructor(message) {
195
+ super(message)
196
+ Error.captureStackTrace(this, ResponseExceededMaxSizeError)
197
+ this.name = 'ResponseExceededMaxSizeError'
198
+ this.message = message || 'Response content exceeded max size'
199
+ this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
200
+ }
201
+ }
202
+
203
+ export class RequestRetryError extends UndiciError {
204
+ constructor(message, code, { headers, data }) {
205
+ super(message)
206
+ Error.captureStackTrace(this, RequestRetryError)
207
+ this.name = 'RequestRetryError'
208
+ this.message = message || 'Request retry error'
209
+ this.code = 'UND_ERR_REQ_RETRY'
210
+ this.statusCode = code
211
+ this.data = data
212
+ this.headers = headers
213
+ }
214
+ }
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import assert from 'node:assert'
2
- import stream from 'node:stream'
3
2
  import createError from 'http-errors'
4
3
  import undici from 'undici'
5
4
  import { findHeader, parseHeaders, AbortError, isStream } from './utils.js'
5
+ import { BodyReadable as Readable } from './readable.js'
6
6
  import CacheableLookup from 'cacheable-lookup'
7
7
 
8
8
  const dispatcherCache = new WeakMap()
@@ -20,125 +20,6 @@ function genReqId() {
20
20
  return `req-${nextReqId.toString(36)}`
21
21
  }
22
22
 
23
- const kAbort = Symbol('abort')
24
- const kStatusCode = Symbol('statusCode')
25
- const kStatusMessage = Symbol('statusMessage')
26
- const kHeaders = Symbol('headers')
27
- const kSize = Symbol('size')
28
- const kHandler = Symbol('handler')
29
-
30
- let ABORT_ERROR
31
-
32
- class Readable extends stream.Readable {
33
- constructor(handler, { statusCode, statusMessage, headers, size, abort, highWaterMark, resume }) {
34
- super({ highWaterMark })
35
-
36
- this[kHandler] = handler
37
- this[kStatusCode] = statusCode
38
- this[kStatusMessage] = statusMessage
39
- this[kHeaders] = headers
40
- this[kSize] = size
41
- this[kAbort] = abort
42
-
43
- this._read = resume
44
- }
45
-
46
- get statusCode() {
47
- return this[kStatusCode]
48
- }
49
-
50
- get statusMessage() {
51
- return this[kStatusMessage]
52
- }
53
-
54
- get headers() {
55
- return this[kHeaders]
56
- }
57
-
58
- get size() {
59
- return this[kSize]
60
- }
61
-
62
- get body() {
63
- return this
64
- }
65
-
66
- _destroy(err, callback) {
67
- if (err == null && !this.readableEnded) {
68
- ABORT_ERROR ??= new AbortError()
69
- err = ABORT_ERROR
70
- }
71
-
72
- if (err) {
73
- this[kAbort](err)
74
- }
75
-
76
- if (this[kHandler].signal) {
77
- this[kHandler].signal.removeEventListener('abort', this[kHandler].onAbort)
78
- this[kHandler].signal = null
79
- }
80
-
81
- // Workaround: https://github.com/nodejs/undici/pull/2497
82
- queueMicrotask(() => {
83
- callback(err)
84
- })
85
- }
86
-
87
- async text() {
88
- const dec = new TextDecoder()
89
- let str = ''
90
-
91
- for await (const chunk of this) {
92
- if (typeof chunk === 'string') {
93
- str += chunk
94
- } else {
95
- str += dec.decode(chunk, { stream: true })
96
- }
97
- }
98
-
99
- // Flush the streaming TextDecoder so that any pending
100
- // incomplete multibyte characters are handled.
101
- str += dec.decode(undefined, { stream: false })
102
- return str
103
- }
104
-
105
- async json() {
106
- return JSON.parse(await this.text())
107
- }
108
-
109
- async arrayBuffer() {
110
- return (await this.buffer()).buffer
111
- }
112
-
113
- async buffer() {
114
- const buffers = []
115
- for await (const chunk of this) {
116
- buffers.push(chunk)
117
- }
118
- return Buffer.concat(buffers)
119
- }
120
-
121
- dump() {
122
- return new Promise((resolve) => {
123
- const rState = this._readableState
124
- if (rState.closeEmitted) {
125
- resolve(null)
126
- } else {
127
- let n = 0
128
- this.on('close', () => resolve(null))
129
- .on('error', () => {})
130
- .on('data', (chunk) => {
131
- n += chunk.length
132
- if (n > 128 * 1024) {
133
- this.destroy()
134
- }
135
- })
136
- .resume()
137
- }
138
- })
139
- }
140
- }
141
-
142
23
  const dispatchers = {
143
24
  responseError: (await import('./interceptor/response-error.js')).default,
144
25
  requestBodyFactory: (await import('./interceptor/request-body-factory.js')).default,
@@ -319,7 +200,8 @@ export async function request(url, opts) {
319
200
  ) {
320
201
  assert(statusCode >= 200)
321
202
 
322
- const contentLength = findHeader(rawHeaders, 'content-length')
203
+ const contentLength = findHeader(headers, 'content-length')
204
+ const contentType = findHeader(headers, 'content-type')
323
205
 
324
206
  this.body = new Readable(this, {
325
207
  resume,
@@ -327,6 +209,7 @@ export async function request(url, opts) {
327
209
  highWaterMark: this.highWaterMark,
328
210
  statusCode,
329
211
  statusMessage,
212
+ contentType,
330
213
  headers,
331
214
  size: Number.isFinite(contentLength) ? contentLength : null,
332
215
  })
@@ -0,0 +1,338 @@
1
+ import assert from 'node:assert'
2
+ import { Readable, isDisturbed } from 'node:stream'
3
+ import {
4
+ RequestAbortedError,
5
+ NotSupportedError,
6
+ InvalidArgumentError,
7
+ AbortError,
8
+ } from './errors.js'
9
+
10
+ let Blob
11
+
12
+ const kConsume = Symbol('kConsume')
13
+ const kReading = Symbol('kReading')
14
+ const kBody = Symbol('kBody')
15
+ const kAbort = Symbol('abort')
16
+ const kContentType = Symbol('kContentType')
17
+
18
+ const kStatusCode = Symbol('kStatusCode')
19
+ const kStatusMessage = Symbol('kStatusMessage')
20
+ const kHeaders = Symbol('kHeaders')
21
+ const kSize = Symbol('kSize')
22
+ const kHandler = Symbol('kHandler')
23
+
24
+ const noop = () => {}
25
+
26
+ let ABORT_ERROR
27
+
28
+ export class BodyReadable extends Readable {
29
+ constructor(
30
+ handler,
31
+ { contentType = '', statusCode, statusMessage, headers, size, abort, highWaterMark, resume },
32
+ ) {
33
+ super({
34
+ autoDestroy: true,
35
+ read: resume,
36
+ highWaterMark,
37
+ })
38
+
39
+ this._readableState.dataEmitted = false
40
+
41
+ this[kHandler] = handler
42
+ this[kStatusCode] = statusCode
43
+ this[kStatusMessage] = statusMessage
44
+ this[kHeaders] = headers
45
+ this[kSize] = size
46
+ this[kAbort] = abort
47
+
48
+ this[kConsume] = null
49
+ this[kBody] = null
50
+ this[kContentType] = contentType
51
+
52
+ // Is stream being consumed through Readable API?
53
+ // This is an optimization so that we avoid checking
54
+ // for 'data' and 'readable' listeners in the hot path
55
+ // inside push().
56
+ this[kReading] = false
57
+ }
58
+
59
+ get statusCode() {
60
+ return this[kStatusCode]
61
+ }
62
+
63
+ get statusMessage() {
64
+ return this[kStatusMessage]
65
+ }
66
+
67
+ get headers() {
68
+ return this[kHeaders]
69
+ }
70
+
71
+ get size() {
72
+ return this[kSize]
73
+ }
74
+
75
+ get body() {
76
+ return this
77
+ }
78
+
79
+ _destroy(err, callback) {
80
+ if (!err && !this._readableState.endEmitted) {
81
+ err = ABORT_ERROR ??= new RequestAbortedError()
82
+ }
83
+
84
+ if (err) {
85
+ this[kAbort]()
86
+ }
87
+
88
+ if (this[kHandler].signal) {
89
+ this[kHandler].signal.removeEventListener('abort', this[kHandler].onAbort)
90
+ this[kHandler].signal = null
91
+ }
92
+
93
+ // Workaround for Node "bug". If the stream is destroyed in same
94
+ // tick as it is created, then a user who is waiting for a
95
+ // promise (i.e micro tick) for installing a 'error' listener will
96
+ // never get a chance and will always encounter an unhandled exception.
97
+ // - tick => process.nextTick(fn)
98
+ // - micro tick => queueMicrotask(fn)
99
+ queueMicrotask(() => {
100
+ callback(err)
101
+ })
102
+ }
103
+
104
+ on(ev, ...args) {
105
+ if (ev === 'data' || ev === 'readable') {
106
+ this[kReading] = true
107
+ }
108
+ return super.on(ev, ...args)
109
+ }
110
+
111
+ addListener(ev, ...args) {
112
+ return this.on(ev, ...args)
113
+ }
114
+
115
+ off(ev, ...args) {
116
+ const ret = super.off(ev, ...args)
117
+ if (ev === 'data' || ev === 'readable') {
118
+ this[kReading] = this.listenerCount('data') > 0 || this.listenerCount('readable') > 0
119
+ }
120
+ return ret
121
+ }
122
+
123
+ removeListener(ev, ...args) {
124
+ return this.off(ev, ...args)
125
+ }
126
+
127
+ push(chunk) {
128
+ if (this[kConsume] && chunk !== null && this.readableLength === 0) {
129
+ consumePush(this[kConsume], chunk)
130
+ return this[kReading] ? super.push(chunk) : true
131
+ }
132
+ return super.push(chunk)
133
+ }
134
+
135
+ // https://fetch.spec.whatwg.org/#dom-body-text
136
+ async text() {
137
+ return consume(this, 'text')
138
+ }
139
+
140
+ // https://fetch.spec.whatwg.org/#dom-body-json
141
+ async json() {
142
+ return consume(this, 'json')
143
+ }
144
+
145
+ // https://fetch.spec.whatwg.org/#dom-body-blob
146
+ async blob() {
147
+ return consume(this, 'blob')
148
+ }
149
+
150
+ // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
151
+ async arrayBuffer() {
152
+ return consume(this, 'arrayBuffer')
153
+ }
154
+
155
+ // https://fetch.spec.whatwg.org/#dom-body-formdata
156
+ async formData() {
157
+ // TODO: Implement.
158
+ throw new NotSupportedError()
159
+ }
160
+
161
+ // https://fetch.spec.whatwg.org/#dom-body-bodyused
162
+ get bodyUsed() {
163
+ return isDisturbed(this)
164
+ }
165
+
166
+ async dump(opts) {
167
+ let limit = Number.isFinite(opts?.limit) ? opts.limit : 262144
168
+ const signal = opts?.signal
169
+
170
+ if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
171
+ throw new InvalidArgumentError('signal must be an AbortSignal')
172
+ }
173
+
174
+ signal?.throwIfAborted()
175
+
176
+ if (this._readableState.closeEmitted) {
177
+ return null
178
+ }
179
+
180
+ return await new Promise((resolve, reject) => {
181
+ const onAbort = () => {
182
+ this.destroy(signal.reason ?? new AbortError())
183
+ }
184
+ signal?.addEventListener('abort', onAbort)
185
+
186
+ this.on('close', function () {
187
+ signal?.removeEventListener('abort', onAbort)
188
+ if (signal?.aborted) {
189
+ reject(signal.reason ?? new AbortError())
190
+ } else {
191
+ resolve(null)
192
+ }
193
+ })
194
+ .on('error', noop)
195
+ .on('data', function (chunk) {
196
+ limit -= chunk.length
197
+ if (limit <= 0) {
198
+ this.destroy()
199
+ }
200
+ })
201
+ .resume()
202
+ })
203
+ }
204
+ }
205
+
206
+ // https://streams.spec.whatwg.org/#readablestream-locked
207
+ function isLocked(self) {
208
+ // Consume is an implicit lock.
209
+ return (self[kBody] && self[kBody].locked === true) || self[kConsume]
210
+ }
211
+
212
+ // https://fetch.spec.whatwg.org/#body-unusable
213
+ function isUnusable(self) {
214
+ return isDisturbed(self) || isLocked(self)
215
+ }
216
+
217
+ async function consume(stream, type) {
218
+ assert(!stream[kConsume])
219
+
220
+ return new Promise((resolve, reject) => {
221
+ if (isUnusable(stream)) {
222
+ const rState = stream._readableState
223
+ if (rState.destroyed && rState.closeEmitted === false) {
224
+ stream
225
+ .on('error', (err) => {
226
+ reject(err)
227
+ })
228
+ .on('close', () => {
229
+ reject(new TypeError('unusable'))
230
+ })
231
+ } else {
232
+ reject(rState.errored ?? new TypeError('unusable'))
233
+ }
234
+ } else {
235
+ stream[kConsume] = {
236
+ type,
237
+ stream,
238
+ resolve,
239
+ reject,
240
+ length: 0,
241
+ body: [],
242
+ }
243
+
244
+ stream
245
+ .on('error', function (err) {
246
+ consumeFinish(this[kConsume], err)
247
+ })
248
+ .on('close', function () {
249
+ if (this[kConsume].body !== null) {
250
+ consumeFinish(this[kConsume], new RequestAbortedError())
251
+ }
252
+ })
253
+
254
+ queueMicrotask(() => consumeStart(stream[kConsume]))
255
+ }
256
+ })
257
+ }
258
+
259
+ function consumeStart(consume) {
260
+ if (consume.body === null) {
261
+ return
262
+ }
263
+
264
+ const { _readableState: state } = consume.stream
265
+
266
+ for (const chunk of state.buffer) {
267
+ consumePush(consume, chunk)
268
+ }
269
+
270
+ if (state.endEmitted) {
271
+ consumeEnd(this[kConsume])
272
+ } else {
273
+ consume.stream.on('end', function () {
274
+ consumeEnd(this[kConsume])
275
+ })
276
+ }
277
+
278
+ consume.stream.resume()
279
+
280
+ while (consume.stream.read() != null) {
281
+ // Loop
282
+ }
283
+ }
284
+
285
+ function consumeEnd(consume) {
286
+ const { type, body, resolve, stream, length } = consume
287
+
288
+ try {
289
+ if (type === 'text') {
290
+ resolve(Buffer.concat(body).toString().toWellFormed())
291
+ } else if (type === 'json') {
292
+ resolve(JSON.parse(Buffer.concat(body)))
293
+ } else if (type === 'arrayBuffer') {
294
+ const dst = new Uint8Array(length)
295
+
296
+ let pos = 0
297
+ for (const buf of body) {
298
+ dst.set(buf, pos)
299
+ pos += buf.byteLength
300
+ }
301
+
302
+ resolve(dst.buffer)
303
+ } else if (type === 'blob') {
304
+ if (!Blob) {
305
+ Blob = require('buffer').Blob
306
+ }
307
+ resolve(new Blob(body, { type: stream[kContentType] }))
308
+ }
309
+
310
+ consumeFinish(consume)
311
+ } catch (err) {
312
+ stream.destroy(err)
313
+ }
314
+ }
315
+
316
+ function consumePush(consume, chunk) {
317
+ consume.length += chunk.length
318
+ consume.body.push(chunk)
319
+ }
320
+
321
+ function consumeFinish(consume, err) {
322
+ if (consume.body === null) {
323
+ return
324
+ }
325
+
326
+ if (err) {
327
+ consume.reject(err)
328
+ } else {
329
+ consume.resolve()
330
+ }
331
+
332
+ consume.type = null
333
+ consume.stream = null
334
+ consume.resolve = null
335
+ consume.reject = null
336
+ consume.length = 0
337
+ consume.body = null
338
+ }
package/lib/utils.js CHANGED
@@ -67,15 +67,13 @@ export function findHeader(headers, name) {
67
67
  for (let i = 0; i < headers.length; i += 2) {
68
68
  const key = headers[i + 0]
69
69
  if (key.length === len && toLowerCase(key.toString()) === name) {
70
- return headers[i + 1].toString()
70
+ return headers[i + 1]?.toString()
71
71
  }
72
72
  }
73
73
  } else if (headers != null) {
74
- {
75
- const val = headers[name]
76
- if (val !== undefined) {
77
- return val
78
- }
74
+ const val = headers[name]
75
+ if (val !== undefined) {
76
+ return val
79
77
  }
80
78
 
81
79
  for (const key of Object.keys(headers)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "2.0.20",
3
+ "version": "2.0.21",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",