got 12.0.0-beta.1 → 12.0.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.
@@ -1,3 +1,3 @@
1
1
  import Request from '../core/index.js';
2
2
  import type { CancelableRequest } from './types.js';
3
- export default function asPromise<T>(firstRequest: Request): CancelableRequest<T>;
3
+ export default function asPromise<T>(firstRequest?: Request): CancelableRequest<T>;
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from 'events';
1
+ import { EventEmitter } from 'node:events';
2
2
  import is from '@sindresorhus/is';
3
3
  import PCancelable from 'p-cancelable';
4
4
  import { HTTPError, RetryError, } from '../core/errors.js';
@@ -38,7 +38,7 @@ export default function asPromise(firstRequest) {
38
38
  request.once('response', async (response) => {
39
39
  // Parse body
40
40
  const contentEncoding = (response.headers['content-encoding'] ?? '').toLowerCase();
41
- const isCompressed = contentEncoding === 'gzip' || contentEncoding === 'defalte' || contentEncoding === 'br';
41
+ const isCompressed = contentEncoding === 'gzip' || contentEncoding === 'deflate' || contentEncoding === 'br';
42
42
  const { options } = request;
43
43
  if (isCompressed && !options.decompress) {
44
44
  response.body = response.rawBody;
@@ -48,7 +48,7 @@ export default function asPromise(firstRequest) {
48
48
  response.body = parseBody(response, options.responseType, options.parseJson, options.encoding);
49
49
  }
50
50
  catch (error) {
51
- // Fallback to `utf8`
51
+ // Fall back to `utf8`
52
52
  response.body = response.rawBody.toString();
53
53
  if (isResponseOk(response)) {
54
54
  request._beforeError(error);
@@ -58,10 +58,7 @@ export default function asPromise(firstRequest) {
58
58
  }
59
59
  try {
60
60
  const hooks = options.hooks.afterResponse;
61
- // TODO: `xo` should detect if `index` is being used for something else
62
- // eslint-disable-next-line unicorn/no-for-loop
63
- for (let index = 0; index < hooks.length; index++) {
64
- const hook = hooks[index];
61
+ for (const [index, hook] of hooks.entries()) {
65
62
  // @ts-expect-error TS doesn't notice that CancelableRequest is a Promise
66
63
  // eslint-disable-next-line no-await-in-loop
67
64
  response = await hook(response, async (updatedOptions) => {
@@ -75,7 +72,7 @@ export default function asPromise(firstRequest) {
75
72
  options.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
76
73
  throw new RetryError(request);
77
74
  });
78
- if (!(is.object(response) && is.number(response.statusCode) && response.body)) {
75
+ if (!(is.object(response) && is.number(response.statusCode) && !is.nullOrUndefined(response.body))) {
79
76
  throw new TypeError('The `afterResponse` hook returned an invalid value');
80
77
  }
81
78
  }
@@ -108,7 +105,6 @@ export default function asPromise(firstRequest) {
108
105
  request.once('error', onError);
109
106
  const previousBody = request.options?.body;
110
107
  request.once('retry', (newRetryCount, error) => {
111
- // @ts-expect-error
112
108
  firstRequest = undefined;
113
109
  const newBody = request.options.body;
114
110
  if (previousBody === newBody && is.nodeStream(newBody)) {
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ import type { Buffer } from 'node:buffer';
2
3
  import PCancelable from 'p-cancelable';
3
4
  import { RequestError } from '../core/errors.js';
4
5
  import type Request from '../core/index.js';
@@ -10,10 +11,28 @@ An error to be thrown when the request is aborted with `.cancel()`.
10
11
  export declare class CancelError extends RequestError {
11
12
  readonly response: Response;
12
13
  constructor(request: Request);
14
+ /**
15
+ Whether the promise is canceled.
16
+ */
13
17
  get isCanceled(): boolean;
14
18
  }
15
19
  export interface CancelableRequest<T extends Response | Response['body'] = Response['body']> extends PCancelable<T>, RequestEvents<CancelableRequest<T>> {
20
+ /**
21
+ A shortcut method that gives a Promise returning a JSON object.
22
+
23
+ It is semantically the same as settings `options.resolveBodyOnly` to `true` and `options.responseType` to `'json'`.
24
+ */
16
25
  json: <ReturnType>() => CancelableRequest<ReturnType>;
26
+ /**
27
+ A shortcut method that gives a Promise returning a [Buffer](https://nodejs.org/api/buffer.html).
28
+
29
+ It is semantically the same as settings `options.resolveBodyOnly` to `true` and `options.responseType` to `'buffer'`.
30
+ */
17
31
  buffer: () => CancelableRequest<Buffer>;
32
+ /**
33
+ A shortcut method that gives a Promise returning a string.
34
+
35
+ It is semantically the same as settings `options.resolveBodyOnly` to `true` and `options.responseType` to `'text'`.
36
+ */
18
37
  text: () => CancelableRequest<string>;
19
38
  }
@@ -8,6 +8,9 @@ export class CancelError extends RequestError {
8
8
  this.name = 'CancelError';
9
9
  this.code = 'ERR_CANCELED';
10
10
  }
11
+ /**
12
+ Whether the promise is canceled.
13
+ */
11
14
  get isCanceled() {
12
15
  return true;
13
16
  }
@@ -93,6 +93,7 @@ export class MaxRedirectsError extends RequestError {
93
93
  An error to be thrown when the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304.
94
94
  Includes a `response` property.
95
95
  */
96
+ // eslint-disable-next-line @typescript-eslint/naming-convention
96
97
  export class HTTPError extends RequestError {
97
98
  constructor(response) {
98
99
  super(`Response code ${response.statusCode} (${response.statusMessage})`, {}, response.request);
@@ -1,9 +1,9 @@
1
1
  /// <reference types="node" />
2
- import { Duplex } from 'stream';
3
- import { URL } from 'url';
4
- import { ServerResponse } from 'http';
5
- import type { ClientRequest } from 'http';
6
- import type { Socket } from 'net';
2
+ import { Duplex } from 'node:stream';
3
+ import { URL } from 'node:url';
4
+ import { ServerResponse } from 'node:http';
5
+ import type { ClientRequest } from 'node:http';
6
+ import type { Socket } from 'node:net';
7
7
  import CacheableRequest from 'cacheable-request';
8
8
  import type { Timings } from '@szmarczak/http-timer';
9
9
  import type ResponseLike from 'responselike';
@@ -12,12 +12,12 @@ import { Response } from './response.js';
12
12
  import { RequestError } from './errors.js';
13
13
  import type { PlainResponse } from './response.js';
14
14
  import type { NativeRequestOptions } from './options.js';
15
+ declare type Error = NodeJS.ErrnoException;
15
16
  export interface Progress {
16
17
  percent: number;
17
18
  transferred: number;
18
19
  total?: number;
19
20
  }
20
- declare type Error = NodeJS.ErrnoException;
21
21
  export declare type GotEventFunction<T> =
22
22
  /**
23
23
  `request` event to get the request object of the request.
@@ -96,7 +96,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
96
96
  private _downloadedSize;
97
97
  private _uploadedSize;
98
98
  private _stopReading;
99
- private _startedReading;
100
99
  private readonly _pipedServerResponses;
101
100
  private _request?;
102
101
  private _responseSize?;
@@ -115,7 +114,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
115
114
  flush(): Promise<void>;
116
115
  _beforeError(error: Error): void;
117
116
  _read(): void;
118
- _write(chunk: any, encoding: string | undefined, callback: (error?: Error | null) => void): void;
117
+ _write(chunk: unknown, encoding: BufferEncoding | undefined, callback: (error?: Error | null) => void): void;
119
118
  _final(callback: (error?: Error | null) => void): void;
120
119
  _destroy(error: Error | null, callback: (error: Error | null) => void): void;
121
120
  pipe<T extends NodeJS.WritableStream>(destination: T, options?: {
@@ -1,11 +1,14 @@
1
- import { Duplex } from 'stream';
2
- import { URL, URLSearchParams } from 'url';
3
- import http, { ServerResponse } from 'http';
1
+ import process from 'node:process';
2
+ import { Buffer } from 'node:buffer';
3
+ import { Duplex } from 'node:stream';
4
+ import { URL, URLSearchParams } from 'node:url';
5
+ import http, { ServerResponse } from 'node:http';
4
6
  import timer from '@szmarczak/http-timer';
5
7
  import CacheableRequest from 'cacheable-request';
6
8
  import decompressResponse from 'decompress-response';
7
9
  import is from '@sindresorhus/is';
8
10
  import { buffer as getBuffer } from 'get-stream';
11
+ import { FormDataEncoder, isFormDataLike } from 'form-data-encoder';
9
12
  import getBodySize from './utils/get-body-size.js';
10
13
  import isFormData from './utils/is-form-data.js';
11
14
  import proxyEvents from './utils/proxy-events.js';
@@ -104,12 +107,6 @@ export default class Request extends Duplex {
104
107
  writable: true,
105
108
  value: void 0
106
109
  });
107
- Object.defineProperty(this, "_startedReading", {
108
- enumerable: true,
109
- configurable: true,
110
- writable: true,
111
- value: void 0
112
- });
113
110
  Object.defineProperty(this, "_pipedServerResponses", {
114
111
  enumerable: true,
115
112
  configurable: true,
@@ -192,7 +189,6 @@ export default class Request extends Duplex {
192
189
  this._downloadedSize = 0;
193
190
  this._uploadedSize = 0;
194
191
  this._stopReading = false;
195
- this._startedReading = false;
196
192
  this._pipedServerResponses = new Set();
197
193
  this._cannotHaveBody = false;
198
194
  this._unproxyEvents = noop;
@@ -228,6 +224,11 @@ export default class Request extends Duplex {
228
224
  Object.assign(this.options.headers, source.headers);
229
225
  }
230
226
  });
227
+ this.on('newListener', event => {
228
+ if (event === 'retry' && this.listenerCount('retry') > 0) {
229
+ throw new Error('A retry listener has been attached already.');
230
+ }
231
+ });
231
232
  try {
232
233
  this.options = new Options(url, options, defaults);
233
234
  if (!this.options.url) {
@@ -384,7 +385,14 @@ export default class Request extends Duplex {
384
385
  return;
385
386
  }
386
387
  this.destroy();
387
- this.emit('retry', this.retryCount + 1, error);
388
+ this.emit('retry', this.retryCount + 1, error, (updatedOptions) => {
389
+ const request = new Request(options.url, updatedOptions, options);
390
+ request.retryCount = this.retryCount + 1;
391
+ process.nextTick(() => {
392
+ void request.flush();
393
+ });
394
+ return request;
395
+ });
388
396
  return;
389
397
  }
390
398
  }
@@ -403,7 +411,6 @@ export default class Request extends Duplex {
403
411
  let data;
404
412
  while ((data = response.read()) !== null) {
405
413
  this._downloadedSize += data.length;
406
- this._startedReading = true;
407
414
  const progress = this.downloadProgress;
408
415
  if (progress.percent < 1) {
409
416
  this.emit('downloadProgress', progress);
@@ -412,7 +419,6 @@ export default class Request extends Duplex {
412
419
  }
413
420
  }
414
421
  }
415
- // Node.js 12 has incorrect types, so the encoding must be a string
416
422
  _write(chunk, encoding, callback) {
417
423
  const write = () => {
418
424
  this._writeRequest(chunk, encoding, callback);
@@ -474,9 +480,6 @@ export default class Request extends Duplex {
474
480
  callback(error);
475
481
  }
476
482
  pipe(destination, options) {
477
- if (this._startedReading) {
478
- throw new Error('Failed to pipe. The response has been emitted already.');
479
- }
480
483
  if (destination instanceof ServerResponse) {
481
484
  this._pipedServerResponses.add(destination);
482
485
  }
@@ -504,6 +507,7 @@ export default class Request extends Duplex {
504
507
  const { options } = this;
505
508
  const { headers } = options;
506
509
  const isForm = !is.undefined(options.form);
510
+ // eslint-disable-next-line @typescript-eslint/naming-convention
507
511
  const isJSON = !is.undefined(options.json);
508
512
  const isBody = !is.undefined(options.body);
509
513
  const cannotHaveBody = methodsWithoutBody.has(options.method) && !(options.method === 'GET' && options.allowGetBody);
@@ -515,6 +519,15 @@ export default class Request extends Duplex {
515
519
  // Serialize body
516
520
  const noContentType = !is.string(headers['content-type']);
517
521
  if (isBody) {
522
+ // Body is spec-compliant FormData
523
+ if (isFormDataLike(options.body)) {
524
+ const encoder = new FormDataEncoder(options.body);
525
+ if (noContentType) {
526
+ headers['content-type'] = encoder.headers['Content-Type'];
527
+ }
528
+ headers['content-length'] = encoder.headers['Content-Length'];
529
+ options.body = encoder.encode();
530
+ }
518
531
  // Special case for https://github.com/form-data/form-data
519
532
  if (isFormData(options.body) && noContentType) {
520
533
  headers['content-type'] = `multipart/form-data; boundary=${options.body.getBoundary()}`;
@@ -837,7 +850,7 @@ export default class Request extends Duplex {
837
850
  if (is.promise(result)) {
838
851
  // We only need to implement the error handler in order to support HTTP2 caching.
839
852
  // The result will be a promise anyway.
840
- // @ts-expect-error
853
+ // @ts-expect-error ignore
841
854
  // eslint-disable-next-line @typescript-eslint/promise-function-async
842
855
  result.once = (event, handler) => {
843
856
  if (event === 'error') {
@@ -901,7 +914,7 @@ export default class Request extends Duplex {
901
914
  }
902
915
  async _makeRequest() {
903
916
  const { options } = this;
904
- const { headers } = options;
917
+ const { headers, username, password } = options;
905
918
  const cookieJar = options.cookieJar;
906
919
  for (const key in headers) {
907
920
  if (is.undefined(headers[key])) {
@@ -915,6 +928,10 @@ export default class Request extends Duplex {
915
928
  if (options.decompress && is.undefined(headers['accept-encoding'])) {
916
929
  headers['accept-encoding'] = supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate';
917
930
  }
931
+ if (username || password) {
932
+ const credentials = Buffer.from(`${username}:${password}`).toString('base64');
933
+ headers.authorization = `Basic ${credentials}`;
934
+ }
918
935
  // Set cookies
919
936
  if (cookieJar) {
920
937
  const cookieString = await cookieJar.getCookieString(options.url.toString());
@@ -947,10 +964,18 @@ export default class Request extends Duplex {
947
964
  // Cache support
948
965
  const fn = options.cache ? this._createCacheableRequest : request;
949
966
  try {
950
- let requestOrResponse = await fn(url, this._requestOptions);
967
+ // We can't do `await fn(...)`,
968
+ // because stream `error` event can be emitted before `Promise.resolve()`.
969
+ let requestOrResponse = fn(url, this._requestOptions);
970
+ if (is.promise(requestOrResponse)) {
971
+ requestOrResponse = await requestOrResponse;
972
+ }
951
973
  // Fallback
952
974
  if (is.undefined(requestOrResponse)) {
953
- requestOrResponse = await options.getFallbackRequestFunction()(url, this._requestOptions);
975
+ requestOrResponse = options.getFallbackRequestFunction()(url, this._requestOptions);
976
+ if (is.promise(requestOrResponse)) {
977
+ requestOrResponse = await requestOrResponse;
978
+ }
954
979
  }
955
980
  if (isClientRequest(requestOrResponse)) {
956
981
  this._onRequest(requestOrResponse);