@nxtedition/nxt-undici 5.1.7 → 5.2.0

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/readable.js DELETED
@@ -1,523 +0,0 @@
1
- // Ported from https://github.com/nodejs/undici/pull/907
2
-
3
- import assert from 'node:assert'
4
- import { Readable } from 'node:stream'
5
- import {
6
- RequestAbortedError,
7
- NotSupportedError,
8
- InvalidArgumentError,
9
- AbortError,
10
- } from './errors.js'
11
- import { isDisturbed } from './utils.js'
12
-
13
- const kConsume = Symbol('kConsume')
14
- const kReading = Symbol('kReading')
15
- const kBody = Symbol('kBody')
16
- const kAbort = Symbol('kAbort')
17
- const kContentType = Symbol('kContentType')
18
- const kContentLength = Symbol('kContentLength')
19
- const kUsed = Symbol('kUsed')
20
- const kBytesRead = Symbol('kBytesRead')
21
-
22
- const noop = () => {}
23
-
24
- /**
25
- * @class
26
- * @extends {Readable}
27
- * @see https://fetch.spec.whatwg.org/#body
28
- */
29
- export class BodyReadable extends Readable {
30
- /**
31
- * @param {object} opts
32
- * @param {(this: Readable, size: number) => void} opts.resume
33
- * @param {() => (void | null)} opts.abort
34
- * @param {string} [opts.contentType = '']
35
- * @param {number} [opts.contentLength]
36
- * @param {number} [opts.highWaterMark = 64 * 1024]
37
- */
38
- constructor({
39
- resume,
40
- abort,
41
- contentType = '',
42
- contentLength,
43
- highWaterMark = 64 * 1024, // Same as nodejs fs streams.
44
- }) {
45
- super({
46
- autoDestroy: true,
47
- read: resume,
48
- highWaterMark,
49
- })
50
-
51
- this._readableState.dataEmitted = false
52
-
53
- this[kAbort] = abort
54
-
55
- /**
56
- * @type {Consume | null}
57
- */
58
- this[kConsume] = null
59
- this[kBytesRead] = 0
60
- /**
61
- * @type {ReadableStream|null}
62
- */
63
- this[kBody] = null
64
- this[kUsed] = false
65
- this[kContentType] = contentType
66
- this[kContentLength] = Number.isFinite(contentLength) ? contentLength : null
67
-
68
- // Is stream being consumed through Readable API?
69
- // This is an optimization so that we avoid checking
70
- // for 'data' and 'readable' listeners in the hot path
71
- // inside push().
72
- this[kReading] = false
73
- }
74
-
75
- /**
76
- * @param {Error|null} err
77
- * @param {(error:(Error|null)) => void} callback
78
- * @returns {void}
79
- */
80
- _destroy(err, callback) {
81
- if (!err && !this._readableState.endEmitted) {
82
- err = new RequestAbortedError()
83
- }
84
-
85
- if (err) {
86
- this[kAbort]()
87
- }
88
-
89
- // Workaround for Node "bug". If the stream is destroyed in same
90
- // tick as it is created, then a user who is waiting for a
91
- // promise (i.e micro tick) for installing an 'error' listener will
92
- // never get a chance and will always encounter an unhandled exception.
93
- if (!this[kUsed]) {
94
- setImmediate(() => {
95
- callback(err)
96
- })
97
- } else {
98
- callback(err)
99
- }
100
- }
101
-
102
- /**
103
- * @param {string} event
104
- * @param {(...args: any[]) => void} listener
105
- * @returns {this}
106
- */
107
- on(event, listener) {
108
- if (event === 'data' || event === 'readable') {
109
- this[kReading] = true
110
- this[kUsed] = true
111
- }
112
- return super.on(event, listener)
113
- }
114
-
115
- /**
116
- * @param {string} event
117
- * @param {(...args: any[]) => void} listener
118
- * @returns {this}
119
- */
120
- addListener(event, listener) {
121
- return this.on(event, listener)
122
- }
123
-
124
- /**
125
- * @param {string|symbol} event
126
- * @param {(...args: any[]) => void} listener
127
- * @returns {this}
128
- */
129
- off(event, listener) {
130
- const ret = super.off(event, listener)
131
- if (event === 'data' || event === 'readable') {
132
- this[kReading] = this.listenerCount('data') > 0 || this.listenerCount('readable') > 0
133
- }
134
- return ret
135
- }
136
-
137
- /**
138
- * @param {string|symbol} event
139
- * @param {(...args: any[]) => void} listener
140
- * @returns {this}
141
- */
142
- removeListener(event, listener) {
143
- return this.off(event, listener)
144
- }
145
-
146
- /**
147
- * @param {Buffer|null} chunk
148
- * @returns {boolean}
149
- */
150
- push(chunk) {
151
- this[kBytesRead] += chunk ? chunk.length : 0
152
-
153
- if (this[kConsume] && chunk !== null) {
154
- consumePush(this[kConsume], chunk)
155
- return this[kReading] ? super.push(chunk) : true
156
- }
157
- return super.push(chunk)
158
- }
159
-
160
- /**
161
- * Consumes and returns the body as a string.
162
- *
163
- * @see https://fetch.spec.whatwg.org/#dom-body-text
164
- * @returns {Promise<string>}
165
- */
166
- text() {
167
- return consume(this, 'text')
168
- }
169
-
170
- /**
171
- * Consumes and returns the body as a JavaScript Object.
172
- *
173
- * @see https://fetch.spec.whatwg.org/#dom-body-json
174
- * @returns {Promise<unknown>}
175
- */
176
- json() {
177
- return consume(this, 'json')
178
- }
179
-
180
- /**
181
- * Consumes and returns the body as a Blob
182
- *
183
- * @see https://fetch.spec.whatwg.org/#dom-body-blob
184
- * @returns {Promise<Blob>}
185
- */
186
- blob() {
187
- return consume(this, 'blob')
188
- }
189
-
190
- /**
191
- * Consumes and returns the body as an Uint8Array.
192
- *
193
- * @see https://fetch.spec.whatwg.org/#dom-body-bytes
194
- * @returns {Promise<Uint8Array>}
195
- */
196
- bytes() {
197
- return consume(this, 'bytes')
198
- }
199
-
200
- /**
201
- * Consumes and returns the body as an ArrayBuffer.
202
- *
203
- * @see https://fetch.spec.whatwg.org/#dom-body-arraybuffer
204
- * @returns {Promise<ArrayBuffer>}
205
- */
206
- arrayBuffer() {
207
- return consume(this, 'arrayBuffer')
208
- }
209
-
210
- /**
211
- * Not implemented
212
- *
213
- * @see https://fetch.spec.whatwg.org/#dom-body-formdata
214
- * @throws {NotSupportedError}
215
- */
216
- async formData() {
217
- // TODO: Implement.
218
- throw new NotSupportedError()
219
- }
220
-
221
- /**
222
- * Returns true if the body is not null and the body has been consumed.
223
- * Otherwise, returns false.
224
- *
225
- * @see https://fetch.spec.whatwg.org/#dom-body-bodyused
226
- * @readonly
227
- * @returns {boolean}
228
- */
229
- get bodyUsed() {
230
- return isDisturbed(this)
231
- }
232
-
233
- /**
234
- * Dumps the response body by reading `limit` number of bytes.
235
- * @param {object} opts
236
- * @param {number} [opts.limit = 131072] Number of bytes to read.
237
- * @param {AbortSignal} [opts.signal] An AbortSignal to cancel the dump.
238
- * @returns {Promise<null>}
239
- */
240
- async dump(opts) {
241
- const signal = opts?.signal
242
-
243
- if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
244
- throw new InvalidArgumentError('signal must be an AbortSignal')
245
- }
246
-
247
- const limit = opts?.limit && Number.isFinite(opts.limit) ? opts.limit : 128 * 1024
248
-
249
- signal?.throwIfAborted()
250
-
251
- if (this._readableState.closeEmitted) {
252
- return null
253
- }
254
-
255
- return await new Promise((resolve, reject) => {
256
- if ((this[kContentLength] && this[kContentLength] > limit) || this[kBytesRead] > limit) {
257
- this.destroy(new AbortError())
258
- }
259
-
260
- if (signal) {
261
- const onAbort = () => {
262
- this.destroy(signal.reason ?? new AbortError())
263
- }
264
- signal.addEventListener('abort', onAbort)
265
- this.on('close', function () {
266
- signal.removeEventListener('abort', onAbort)
267
- if (signal.aborted) {
268
- reject(signal.reason ?? new AbortError())
269
- } else {
270
- resolve(null)
271
- }
272
- })
273
- } else {
274
- this.on('close', resolve)
275
- }
276
-
277
- this.on('error', noop)
278
- .on('data', () => {
279
- if (this[kBytesRead] > limit) {
280
- this.destroy()
281
- }
282
- })
283
- .resume()
284
- })
285
- }
286
-
287
- /**
288
- * @param {BufferEncoding} encoding
289
- * @returns {this}
290
- */
291
- setEncoding(encoding) {
292
- if (Buffer.isEncoding(encoding)) {
293
- this._readableState.encoding = encoding
294
- }
295
- return this
296
- }
297
- }
298
-
299
- /**
300
- * @see https://streams.spec.whatwg.org/#readablestream-locked
301
- * @param {BodyReadable} bodyReadable
302
- * @returns {boolean}
303
- */
304
- function isLocked(bodyReadable) {
305
- // Consume is an implicit lock.
306
- return bodyReadable[kBody]?.locked === true || bodyReadable[kConsume] !== null
307
- }
308
-
309
- /**
310
- * @see https://fetch.spec.whatwg.org/#body-unusable
311
- * @param {BodyReadable} bodyReadable
312
- * @returns {boolean}
313
- */
314
- function isUnusable(bodyReadable) {
315
- return isDisturbed(bodyReadable) || isLocked(bodyReadable)
316
- }
317
-
318
- /**
319
- * @typedef {object} Consume
320
- * @property {string} type
321
- * @property {BodyReadable} stream
322
- * @property {((value?: any) => void)} resolve
323
- * @property {((err: Error) => void)} reject
324
- * @property {number} length
325
- * @property {Buffer[]} body
326
- */
327
-
328
- /**
329
- * @param {BodyReadable} stream
330
- * @param {string} type
331
- * @returns {Promise<any>}
332
- */
333
- function consume(stream, type) {
334
- assert(!stream[kConsume])
335
-
336
- return new Promise((resolve, reject) => {
337
- if (isUnusable(stream)) {
338
- const rState = stream._readableState
339
- if (rState.destroyed && rState.closeEmitted === false) {
340
- stream
341
- .on('error', (err) => {
342
- reject(err)
343
- })
344
- .on('close', () => {
345
- reject(new TypeError('unusable'))
346
- })
347
- } else {
348
- reject(rState.errored ?? new TypeError('unusable'))
349
- }
350
- } else {
351
- queueMicrotask(() => {
352
- stream[kConsume] = {
353
- type,
354
- stream,
355
- resolve,
356
- reject,
357
- length: 0,
358
- body: [],
359
- }
360
-
361
- stream
362
- .on('error', function (err) {
363
- consumeFinish(this[kConsume], err)
364
- })
365
- .on('close', function () {
366
- if (this[kConsume].body !== null) {
367
- consumeFinish(this[kConsume], new RequestAbortedError())
368
- }
369
- })
370
-
371
- consumeStart(stream[kConsume])
372
- })
373
- }
374
- })
375
- }
376
-
377
- /**
378
- * @param {Consume} consume
379
- * @returns {void}
380
- */
381
- function consumeStart(consume) {
382
- if (consume.body === null) {
383
- return
384
- }
385
-
386
- const { _readableState: state } = consume.stream
387
-
388
- if (state.bufferIndex) {
389
- const start = state.bufferIndex
390
- const end = state.buffer.length
391
- for (let n = start; n < end; n++) {
392
- consumePush(consume, state.buffer[n])
393
- }
394
- } else {
395
- for (const chunk of state.buffer) {
396
- consumePush(consume, chunk)
397
- }
398
- }
399
-
400
- if (state.endEmitted) {
401
- consumeEnd(this[kConsume], this._readableState.encoding)
402
- } else {
403
- consume.stream.on('end', function () {
404
- consumeEnd(this[kConsume], this._readableState.encoding)
405
- })
406
- }
407
-
408
- consume.stream.resume()
409
-
410
- while (consume.stream.read() != null) {
411
- // Loop
412
- }
413
- }
414
-
415
- /**
416
- * @param {Buffer[]} chunks
417
- * @param {number} length
418
- * @param {BufferEncoding} encoding
419
- * @returns {string}
420
- */
421
- function chunksDecode(chunks, length, encoding) {
422
- if (chunks.length === 0 || length === 0) {
423
- return ''
424
- }
425
- const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length)
426
- const bufferLength = buffer.length
427
-
428
- // Skip BOM.
429
- const start =
430
- bufferLength > 2 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf ? 3 : 0
431
- if (!encoding || encoding === 'utf8' || encoding === 'utf-8') {
432
- return buffer.utf8Slice(start, bufferLength)
433
- } else {
434
- return buffer.subarray(start, bufferLength).toString(encoding)
435
- }
436
- }
437
-
438
- /**
439
- * @param {Buffer[]} chunks
440
- * @param {number} length
441
- * @returns {Uint8Array}
442
- */
443
- function chunksConcat(chunks, length) {
444
- if (chunks.length === 0 || length === 0) {
445
- return new Uint8Array(0)
446
- }
447
- if (chunks.length === 1) {
448
- // fast-path
449
- return new Uint8Array(chunks[0])
450
- }
451
- const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
452
-
453
- let offset = 0
454
- for (let i = 0; i < chunks.length; ++i) {
455
- const chunk = chunks[i]
456
- buffer.set(chunk, offset)
457
- offset += chunk.length
458
- }
459
-
460
- return buffer
461
- }
462
-
463
- /**
464
- * @param {Consume} consume
465
- * @param {BufferEncoding} encoding
466
- * @returns {void}
467
- */
468
- function consumeEnd(consume, encoding) {
469
- const { type, body, resolve, stream, length } = consume
470
-
471
- try {
472
- if (type === 'text') {
473
- resolve(chunksDecode(body, length, encoding))
474
- } else if (type === 'json') {
475
- resolve(JSON.parse(chunksDecode(body, length, encoding)))
476
- } else if (type === 'arrayBuffer') {
477
- resolve(chunksConcat(body, length).buffer)
478
- } else if (type === 'blob') {
479
- resolve(new Blob(body, { type: stream[kContentType] }))
480
- } else if (type === 'bytes') {
481
- resolve(chunksConcat(body, length))
482
- }
483
-
484
- consumeFinish(consume)
485
- } catch (err) {
486
- stream.destroy(err)
487
- }
488
- }
489
-
490
- /**
491
- * @param {Consume} consume
492
- * @param {Buffer} chunk
493
- * @returns {void}
494
- */
495
- function consumePush(consume, chunk) {
496
- consume.length += chunk.length
497
- consume.body.push(chunk)
498
- }
499
-
500
- /**
501
- * @param {Consume} consume
502
- * @param {Error} [err]
503
- * @returns {void}
504
- */
505
- function consumeFinish(consume, err) {
506
- if (consume.body === null) {
507
- return
508
- }
509
-
510
- if (err) {
511
- consume.reject(err)
512
- } else {
513
- consume.resolve()
514
- }
515
-
516
- // Reset the consume object to allow for garbage collection.
517
- consume.type = null
518
- consume.stream = null
519
- consume.resolve = null
520
- consume.reject = null
521
- consume.length = 0
522
- consume.body = null
523
- }