got 14.6.0 → 14.6.2

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.
@@ -67,8 +67,9 @@ export type BeforeRequestHookContext = {
67
67
  };
68
68
  export type BeforeRequestHook = (options: NormalizedOptions, context: BeforeRequestHookContext) => Promisable<void | Response | ResponseLike>;
69
69
  export type BeforeRedirectHook = (updatedOptions: NormalizedOptions, plainResponse: PlainResponse) => Promisable<void>;
70
- export type BeforeErrorHook = (error: RequestError) => Promisable<RequestError>;
70
+ export type BeforeErrorHook = (error: RequestError) => Promisable<Error>;
71
71
  export type BeforeRetryHook = (error: RequestError, retryCount: number) => Promisable<void>;
72
+ export type BeforeCacheHook = (response: PlainResponse) => false | void;
72
73
  export type AfterResponseHook<ResponseType = unknown> = (response: Response<ResponseType>, retryWithMergedOptions: (options: OptionsInit) => never) => Promisable<Response | CancelableRequest<Response>>;
73
74
  /**
74
75
  All available hooks of Got.
@@ -255,13 +256,20 @@ export type Hooks = {
255
256
  /**
256
257
  Called with a `RequestError` instance. The error is passed to the hook right before it's thrown.
257
258
 
258
- This is especially useful when you want to have more detailed errors.
259
+ This hook can return any `Error` instance, allowing you to:
260
+ - Return custom error classes for better error handling in your application
261
+ - Extend `RequestError` with additional properties
262
+ - Return plain `Error` instances when you don't need Got-specific error information
263
+
264
+ This is especially useful when you want to have more detailed errors or maintain backward compatibility with existing error handling code.
259
265
 
260
266
  @default []
261
267
 
268
+ @example
262
269
  ```
263
270
  import got from 'got';
264
271
 
272
+ // Modify and return the error
265
273
  await got('https://api.github.com/repos/sindresorhus/got/commits', {
266
274
  responseType: 'json',
267
275
  hooks: {
@@ -278,6 +286,29 @@ export type Hooks = {
278
286
  ]
279
287
  }
280
288
  });
289
+
290
+ // Return a custom error class
291
+ class CustomAPIError extends Error {
292
+ constructor(message, statusCode) {
293
+ super(message);
294
+ this.name = 'CustomAPIError';
295
+ this.statusCode = statusCode;
296
+ }
297
+ }
298
+
299
+ await got('https://api.example.com/endpoint', {
300
+ hooks: {
301
+ beforeError: [
302
+ error => {
303
+ // Return a custom error for backward compatibility with your application
304
+ return new CustomAPIError(
305
+ error.message,
306
+ error.response?.statusCode
307
+ );
308
+ }
309
+ ]
310
+ }
311
+ });
281
312
  ```
282
313
  */
283
314
  beforeError: BeforeErrorHook[];
@@ -315,6 +346,72 @@ export type Hooks = {
315
346
  */
316
347
  beforeRetry: BeforeRetryHook[];
317
348
  /**
349
+ Called right before the response is cached. Allows you to control caching behavior by directly modifying the response or preventing caching.
350
+
351
+ This is especially useful when you want to prevent caching of specific responses or modify cache headers.
352
+
353
+ @default []
354
+
355
+ **Return value:**
356
+ > - `false` - Prevent caching (remaining hooks are skipped)
357
+ > - `void`/`undefined` - Use default caching behavior (mutations take effect)
358
+
359
+ **Modifying the response:**
360
+ > - Hooks can directly mutate response properties like `headers`, `statusCode`, and `statusMessage`
361
+ > - Mutations to `response.headers` affect how the caching layer decides whether to cache the response and for how long
362
+ > - Changes are applied to what gets cached
363
+
364
+ **Note:**
365
+ > - This hook is only called when the `cache` option is enabled.
366
+
367
+ **Note:**
368
+ > - This hook must be synchronous. It cannot return a Promise. If you need async logic to determine caching behavior, use a `beforeRequest` hook instead.
369
+
370
+ **Note:**
371
+ > - When returning `false`, remaining hooks are skipped and the response will not be cached.
372
+
373
+ **Note:**
374
+ > - Returning anything other than `false` or `undefined` will throw a TypeError.
375
+
376
+ **Note:**
377
+ > - If a hook throws an error, it will be propagated and the request will fail. This is consistent with how other hooks in Got handle errors.
378
+
379
+ **Note:**
380
+ > - At this stage, the response body has not been read yet - it's still a stream. Properties like `response.body` and `response.rawBody` are not available. You can only inspect/modify response headers and status code.
381
+
382
+ @example
383
+ ```
384
+ import got from 'got';
385
+
386
+ // Simple: Don't cache errors
387
+ const instance = got.extend({
388
+ cache: new Map(),
389
+ hooks: {
390
+ beforeCache: [
391
+ (response) => response.statusCode >= 400 ? false : undefined
392
+ ]
393
+ }
394
+ });
395
+
396
+ // Advanced: Modify headers for fine control
397
+ const instance2 = got.extend({
398
+ cache: new Map(),
399
+ hooks: {
400
+ beforeCache: [
401
+ (response) => {
402
+ // Force caching with explicit duration
403
+ // Mutations work directly - no need to return
404
+ response.headers['cache-control'] = 'public, max-age=3600';
405
+ }
406
+ ]
407
+ }
408
+ });
409
+
410
+ await instance('https://example.com');
411
+ ```
412
+ */
413
+ beforeCache: BeforeCacheHook[];
414
+ /**
318
415
  Each function should return the response. This is especially useful when you want to refresh an access token.
319
416
 
320
417
  @default []
@@ -1057,6 +1154,57 @@ export default class Options {
1057
1154
  get allowGetBody(): boolean;
1058
1155
  set allowGetBody(value: boolean);
1059
1156
  /**
1157
+ Automatically copy headers from piped streams.
1158
+
1159
+ When piping a request into a Got stream (e.g., `request.pipe(got.stream(url))`), this controls whether headers from the source stream are automatically merged into the Got request headers.
1160
+
1161
+ Note: Piped headers overwrite any explicitly set headers with the same name. To override this, either set `copyPipedHeaders` to `false` and manually copy safe headers, or use a `beforeRequest` hook to force specific header values after piping.
1162
+
1163
+ Useful for proxy scenarios, but you may want to disable this to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1164
+
1165
+ @default true
1166
+
1167
+ @example
1168
+ ```
1169
+ import got from 'got';
1170
+ import {pipeline} from 'node:stream/promises';
1171
+
1172
+ // Disable automatic header copying and manually copy only safe headers
1173
+ server.get('/proxy', async (request, response) => {
1174
+ const gotStream = got.stream('https://example.com', {
1175
+ copyPipedHeaders: false,
1176
+ headers: {
1177
+ 'user-agent': request.headers['user-agent'],
1178
+ 'accept': request.headers['accept'],
1179
+ // Explicitly NOT copying host, connection, authorization, etc.
1180
+ }
1181
+ });
1182
+
1183
+ await pipeline(request, gotStream, response);
1184
+ });
1185
+ ```
1186
+
1187
+ @example
1188
+ ```
1189
+ import got from 'got';
1190
+
1191
+ // Override piped headers using beforeRequest hook
1192
+ const gotStream = got.stream('https://example.com', {
1193
+ hooks: {
1194
+ beforeRequest: [
1195
+ options => {
1196
+ // Force specific header values after piping
1197
+ options.headers.host = 'example.com';
1198
+ delete options.headers.authorization;
1199
+ }
1200
+ ]
1201
+ }
1202
+ });
1203
+ ```
1204
+ */
1205
+ get copyPipedHeaders(): boolean;
1206
+ set copyPipedHeaders(value: boolean);
1207
+ /**
1060
1208
  Request headers.
1061
1209
 
1062
1210
  Existing headers will be overwritten. Headers set to `undefined` will be omitted.
@@ -1315,6 +1463,7 @@ export default class Options {
1315
1463
  throwHttpErrors: boolean;
1316
1464
  http2: boolean;
1317
1465
  allowGetBody: boolean;
1466
+ copyPipedHeaders: boolean;
1318
1467
  methodRewriting: boolean;
1319
1468
  dnsLookupIpVersion: DnsLookupIpVersion;
1320
1469
  parseJson: ParseJsonFunction;
@@ -1379,7 +1528,6 @@ export default class Options {
1379
1528
  _defaultAgent?: http.Agent | undefined;
1380
1529
  auth?: string | null | undefined;
1381
1530
  defaultPort?: number | string | undefined;
1382
- hints?: import("dns").LookupOptions["hints"];
1383
1531
  host?: string | null | undefined;
1384
1532
  hostname?: string | null | undefined;
1385
1533
  insecureHTTPParser?: boolean | undefined;
@@ -1391,7 +1539,8 @@ export default class Options {
1391
1539
  signal?: AbortSignal | undefined;
1392
1540
  socketPath?: string | undefined;
1393
1541
  uniqueHeaders?: Array<string | string[]> | undefined;
1394
- joinDuplicateHeaders?: boolean;
1542
+ joinDuplicateHeaders?: boolean | undefined;
1543
+ hints?: number | undefined;
1395
1544
  ALPNCallback?: ((arg: {
1396
1545
  servername: string;
1397
1546
  protocols: string[];
@@ -127,6 +127,7 @@ const defaultInternals = {
127
127
  beforeError: [],
128
128
  beforeRedirect: [],
129
129
  beforeRetry: [],
130
+ beforeCache: [],
130
131
  afterResponse: [],
131
132
  },
132
133
  followRedirect: true,
@@ -137,6 +138,7 @@ const defaultInternals = {
137
138
  password: '',
138
139
  http2: false,
139
140
  allowGetBody: false,
141
+ copyPipedHeaders: true,
140
142
  headers: {
141
143
  'user-agent': 'got (https://github.com/sindresorhus/got)',
142
144
  },
@@ -274,14 +276,12 @@ const cloneInternals = (internals) => {
274
276
  beforeError: [...hooks.beforeError],
275
277
  beforeRedirect: [...hooks.beforeRedirect],
276
278
  beforeRetry: [...hooks.beforeRetry],
279
+ beforeCache: [...hooks.beforeCache],
277
280
  afterResponse: [...hooks.afterResponse],
278
281
  },
279
282
  searchParams: internals.searchParams ? new URLSearchParams(internals.searchParams) : undefined,
280
283
  pagination: { ...internals.pagination },
281
284
  };
282
- if (result.url !== undefined) {
283
- result.prefixUrl = '';
284
- }
285
285
  return result;
286
286
  };
287
287
  const cloneRaw = (raw) => {
@@ -339,11 +339,24 @@ const cloneRaw = (raw) => {
339
339
  if (is.array(hooks.beforeRetry)) {
340
340
  result.hooks.beforeRetry = [...hooks.beforeRetry];
341
341
  }
342
+ if (is.array(hooks.beforeCache)) {
343
+ result.hooks.beforeCache = [...hooks.beforeCache];
344
+ }
342
345
  if (is.array(hooks.afterResponse)) {
343
346
  result.hooks.afterResponse = [...hooks.afterResponse];
344
347
  }
345
348
  }
346
- // TODO: raw.searchParams
349
+ if (raw.searchParams) {
350
+ if (is.string(raw.searchParams)) {
351
+ result.searchParams = raw.searchParams;
352
+ }
353
+ else if (raw.searchParams instanceof URLSearchParams) {
354
+ result.searchParams = new URLSearchParams(raw.searchParams);
355
+ }
356
+ else if (is.object(raw.searchParams)) {
357
+ result.searchParams = { ...raw.searchParams };
358
+ }
359
+ }
347
360
  if (is.object(raw.pagination)) {
348
361
  result.pagination = { ...raw.pagination };
349
362
  }
@@ -367,7 +380,7 @@ const init = (options, withOptions, self) => {
367
380
  export default class Options {
368
381
  _unixOptions;
369
382
  _internals;
370
- _merging;
383
+ _merging = false;
371
384
  _init;
372
385
  constructor(input, options, defaults) {
373
386
  assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
@@ -378,8 +391,6 @@ export default class Options {
378
391
  }
379
392
  this._internals = cloneInternals(defaults?._internals ?? defaults ?? defaultInternals);
380
393
  this._init = [...(defaults?._init ?? [])];
381
- this._merging = false;
382
- this._unixOptions = undefined;
383
394
  // This rule allows `finally` to be considered more important.
384
395
  // Meaning no matter the error thrown in the `try` block,
385
396
  // if `finally` throws then the `finally` error will be thrown.
@@ -768,7 +779,11 @@ export default class Options {
768
779
  if (is.string(value) && value.startsWith('/')) {
769
780
  throw new Error('`url` must not start with a slash');
770
781
  }
771
- const urlString = `${this.prefixUrl}${value.toString()}`;
782
+ // Detect if URL is already absolute (has a protocol/scheme)
783
+ const valueString = value.toString();
784
+ const isAbsolute = is.urlInstance(value) || /^[a-z][a-z\d+.-]*:\/\//i.test(valueString);
785
+ // Only concatenate prefixUrl if the URL is relative
786
+ const urlString = isAbsolute ? valueString : `${this.prefixUrl}${valueString}`;
772
787
  const url = new URL(urlString);
773
788
  this._internals.url = url;
774
789
  if (url.protocol === 'unix:') {
@@ -1222,6 +1237,62 @@ export default class Options {
1222
1237
  this._internals.allowGetBody = value;
1223
1238
  }
1224
1239
  /**
1240
+ Automatically copy headers from piped streams.
1241
+
1242
+ When piping a request into a Got stream (e.g., `request.pipe(got.stream(url))`), this controls whether headers from the source stream are automatically merged into the Got request headers.
1243
+
1244
+ Note: Piped headers overwrite any explicitly set headers with the same name. To override this, either set `copyPipedHeaders` to `false` and manually copy safe headers, or use a `beforeRequest` hook to force specific header values after piping.
1245
+
1246
+ Useful for proxy scenarios, but you may want to disable this to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1247
+
1248
+ @default true
1249
+
1250
+ @example
1251
+ ```
1252
+ import got from 'got';
1253
+ import {pipeline} from 'node:stream/promises';
1254
+
1255
+ // Disable automatic header copying and manually copy only safe headers
1256
+ server.get('/proxy', async (request, response) => {
1257
+ const gotStream = got.stream('https://example.com', {
1258
+ copyPipedHeaders: false,
1259
+ headers: {
1260
+ 'user-agent': request.headers['user-agent'],
1261
+ 'accept': request.headers['accept'],
1262
+ // Explicitly NOT copying host, connection, authorization, etc.
1263
+ }
1264
+ });
1265
+
1266
+ await pipeline(request, gotStream, response);
1267
+ });
1268
+ ```
1269
+
1270
+ @example
1271
+ ```
1272
+ import got from 'got';
1273
+
1274
+ // Override piped headers using beforeRequest hook
1275
+ const gotStream = got.stream('https://example.com', {
1276
+ hooks: {
1277
+ beforeRequest: [
1278
+ options => {
1279
+ // Force specific header values after piping
1280
+ options.headers.host = 'example.com';
1281
+ delete options.headers.authorization;
1282
+ }
1283
+ ]
1284
+ }
1285
+ });
1286
+ ```
1287
+ */
1288
+ get copyPipedHeaders() {
1289
+ return this._internals.copyPipedHeaders;
1290
+ }
1291
+ set copyPipedHeaders(value) {
1292
+ assert.boolean(value);
1293
+ this._internals.copyPipedHeaders = value;
1294
+ }
1295
+ /**
1225
1296
  Request headers.
1226
1297
 
1227
1298
  Existing headers will be overwritten. Headers set to `undefined` will be omitted.
@@ -101,6 +101,8 @@ An error to be thrown when server response code is 2xx, and parsing body fails.
101
101
  Includes a `response` property.
102
102
  */
103
103
  export declare class ParseError extends RequestError {
104
+ name: string;
105
+ code: string;
104
106
  readonly response: Response;
105
107
  constructor(error: Error, response: Response);
106
108
  }
@@ -11,11 +11,11 @@ An error to be thrown when server response code is 2xx, and parsing body fails.
11
11
  Includes a `response` property.
12
12
  */
13
13
  export class ParseError extends RequestError {
14
+ name = 'ParseError';
15
+ code = 'ERR_BODY_PARSE_FAILURE';
14
16
  constructor(error, response) {
15
17
  const { options } = response.request;
16
18
  super(`${error.message} in "${options.url.toString()}"`, error, response.request);
17
- this.name = 'ParseError';
18
- this.code = 'ERR_BODY_PARSE_FAILURE';
19
19
  }
20
20
  }
21
21
  export const parseBody = (response, responseType, parseJson, encoding) => {
@@ -18,6 +18,7 @@ export type Delays = {
18
18
  export type ErrorCode = 'ETIMEDOUT' | 'ECONNRESET' | 'EADDRINUSE' | 'ECONNREFUSED' | 'EPIPE' | 'ENOTFOUND' | 'ENETUNREACH' | 'EAI_AGAIN';
19
19
  export declare class TimeoutError extends Error {
20
20
  event: string;
21
+ name: string;
21
22
  code: ErrorCode;
22
23
  constructor(threshold: number, event: string);
23
24
  }
@@ -4,12 +4,11 @@ const reentry = Symbol('reentry');
4
4
  const noop = () => { };
5
5
  export class TimeoutError extends Error {
6
6
  event;
7
- code;
7
+ name = 'TimeoutError';
8
+ code = 'ETIMEDOUT';
8
9
  constructor(threshold, event) {
9
10
  super(`Timeout awaiting '${event}' for ${threshold}ms`);
10
11
  this.event = event;
11
- this.name = 'TimeoutError';
12
- this.code = 'ETIMEDOUT';
13
12
  }
14
13
  }
15
14
  export default function timedOut(request, delays, options) {
@@ -1,4 +1,3 @@
1
- import { Buffer } from 'node:buffer';
2
1
  import { promisify } from 'node:util';
3
2
  import is from '@sindresorhus/is';
4
3
  import isFormData from './is-form-data.js';
@@ -10,7 +9,7 @@ export default async function getBodySize(body, headers) {
10
9
  return 0;
11
10
  }
12
11
  if (is.string(body)) {
13
- return Buffer.byteLength(body);
12
+ return new TextEncoder().encode(body).byteLength;
14
13
  }
15
14
  if (is.buffer(body)) {
16
15
  return body.length;
@@ -1,7 +1,6 @@
1
1
  export default class WeakableMap<K, V> {
2
2
  weakMap: WeakMap<Record<string, unknown>, V>;
3
3
  map: Map<K, V>;
4
- constructor();
5
4
  set(key: K, value: V): void;
6
5
  get(key: K): V | undefined;
7
6
  has(key: K): boolean;
@@ -1,10 +1,6 @@
1
1
  export default class WeakableMap {
2
- weakMap;
3
- map;
4
- constructor() {
5
- this.weakMap = new WeakMap();
6
- this.map = new Map();
7
- }
2
+ weakMap = new WeakMap();
3
+ map = new Map();
8
4
  set(key, value) {
9
5
  if (typeof key === 'object') {
10
6
  this.weakMap.set(key, value);
@@ -7,6 +7,7 @@ export * from './core/response.js';
7
7
  export type { default as Request } from './core/index.js';
8
8
  export * from './core/index.js';
9
9
  export * from './core/errors.js';
10
+ export * from './core/diagnostics-channel.js';
10
11
  export type { Delays } from './core/timed-out.js';
11
12
  export { default as calculateRetryDelay } from './core/calculate-retry-delay.js';
12
13
  export * from './as-promise/types.js';
@@ -14,6 +14,7 @@ export * from './core/options.js';
14
14
  export * from './core/response.js';
15
15
  export * from './core/index.js';
16
16
  export * from './core/errors.js';
17
+ export * from './core/diagnostics-channel.js';
17
18
  export { default as calculateRetryDelay } from './core/calculate-retry-delay.js';
18
19
  export * from './as-promise/types.js';
19
20
  export * from './types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "got",
3
- "version": "14.6.0",
3
+ "version": "14.6.2",
4
4
  "description": "Human-friendly and powerful HTTP request library for Node.js",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/got",
@@ -53,6 +53,7 @@
53
53
  "dependencies": {
54
54
  "@sindresorhus/is": "^7.0.1",
55
55
  "@szmarczak/http-timer": "^5.0.1",
56
+ "byte-counter": "^0.1.0",
56
57
  "cacheable-lookup": "^7.0.0",
57
58
  "cacheable-request": "^13.0.12",
58
59
  "decompress-response": "^10.0.0",
@@ -82,6 +83,7 @@
82
83
  "bluebird": "^3.7.2",
83
84
  "body-parser": "^1.20.3",
84
85
  "c8": "^10.1.3",
86
+ "chunk-data": "^0.1.0",
85
87
  "create-cert": "^1.0.6",
86
88
  "create-test-server": "^3.0.1",
87
89
  "del-cli": "^6.0.0",