got 14.4.9 → 14.6.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.
@@ -11,11 +11,43 @@ import http2wrapper from 'http2-wrapper';
11
11
  import { isFormData } from 'form-data-encoder';
12
12
  import parseLinkHeader from './parse-link-header.js';
13
13
  const [major, minor] = process.versions.node.split('.').map(Number);
14
+ /**
15
+ Generic helper that wraps any assertion function to add context to error messages.
16
+ */
17
+ function wrapAssertionWithContext(optionName, assertionFn) {
18
+ try {
19
+ assertionFn();
20
+ }
21
+ catch (error) {
22
+ if (error instanceof Error) {
23
+ error.message = `Option '${optionName}': ${error.message}`;
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+ /**
29
+ Helper function that wraps assert.any() to provide better error messages.
30
+ When assertion fails, it includes the option name in the error message.
31
+ */
32
+ function assertAny(optionName, validators, value) {
33
+ wrapAssertionWithContext(optionName, () => {
34
+ assert.any(validators, value);
35
+ });
36
+ }
37
+ /**
38
+ Helper function that wraps assert.plainObject() to provide better error messages.
39
+ When assertion fails, it includes the option name in the error message.
40
+ */
41
+ function assertPlainObject(optionName, value) {
42
+ wrapAssertionWithContext(optionName, () => {
43
+ assert.plainObject(value);
44
+ });
45
+ }
14
46
  function validateSearchParameters(searchParameters) {
15
47
  // eslint-disable-next-line guard-for-in
16
48
  for (const key in searchParameters) {
17
49
  const value = searchParameters[key];
18
- assert.any([is.string, is.number, is.boolean, is.null, is.undefined], value);
50
+ assertAny(`searchParams.${key}`, [is.string, is.number, is.boolean, is.null, is.undefined], value);
19
51
  }
20
52
  }
21
53
  const globalCache = new Map();
@@ -27,6 +59,39 @@ const getGlobalDnsCache = () => {
27
59
  globalDnsCache = new CacheableLookup();
28
60
  return globalDnsCache;
29
61
  };
62
+ // Detects and wraps QuickLRU v7+ instances to make them compatible with the StorageAdapter interface
63
+ const wrapQuickLruIfNeeded = (value) => {
64
+ // Check if this is QuickLRU v7+ using Symbol.toStringTag and the evict method (added in v7)
65
+ if (value?.[Symbol.toStringTag] === 'QuickLRU' && typeof value.evict === 'function') {
66
+ // QuickLRU v7+ uses set(key, value, {maxAge: number}) but StorageAdapter expects set(key, value, ttl)
67
+ // Wrap it to translate the interface
68
+ return {
69
+ get(key) {
70
+ return value.get(key);
71
+ },
72
+ set(key, cacheValue, ttl) {
73
+ if (ttl === undefined) {
74
+ value.set(key, cacheValue);
75
+ }
76
+ else {
77
+ value.set(key, cacheValue, { maxAge: ttl });
78
+ }
79
+ return true;
80
+ },
81
+ delete(key) {
82
+ return value.delete(key);
83
+ },
84
+ clear() {
85
+ return value.clear();
86
+ },
87
+ has(key) {
88
+ return value.has(key);
89
+ },
90
+ };
91
+ }
92
+ // QuickLRU v5 and other caches work as-is
93
+ return value;
94
+ };
30
95
  const defaultInternals = {
31
96
  request: undefined,
32
97
  agent: {
@@ -115,6 +180,8 @@ const defaultInternals = {
115
180
  calculateDelay: ({ computedValue }) => computedValue,
116
181
  backoffLimit: Number.POSITIVE_INFINITY,
117
182
  noise: 100,
183
+ // TODO: Change default to `true` in the next major version to fix https://github.com/sindresorhus/got/issues/2243
184
+ enforceRetryRules: false,
118
185
  },
119
186
  localAddress: undefined,
120
187
  method: 'GET',
@@ -129,6 +196,7 @@ const defaultInternals = {
129
196
  alpnProtocols: undefined,
130
197
  rejectUnauthorized: undefined,
131
198
  checkServerIdentity: undefined,
199
+ serverName: undefined,
132
200
  certificateAuthority: undefined,
133
201
  key: undefined,
134
202
  certificate: undefined,
@@ -143,6 +211,7 @@ const defaultInternals = {
143
211
  dhparam: undefined,
144
212
  ecdhCurve: undefined,
145
213
  certificateRevocationLists: undefined,
214
+ secureOptions: undefined,
146
215
  },
147
216
  encoding: undefined,
148
217
  resolveBodyOnly: false,
@@ -181,6 +250,7 @@ const defaultInternals = {
181
250
  maxHeaderSize: undefined,
182
251
  signal: undefined,
183
252
  enableUnixSockets: false,
253
+ strictContentLength: false,
184
254
  };
185
255
  const cloneInternals = (internals) => {
186
256
  const { hooks, retry } = internals;
@@ -300,9 +370,9 @@ export default class Options {
300
370
  _merging;
301
371
  _init;
302
372
  constructor(input, options, defaults) {
303
- assert.any([is.string, is.urlInstance, is.object, is.undefined], input);
304
- assert.any([is.object, is.undefined], options);
305
- assert.any([is.object, is.undefined], defaults);
373
+ assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
374
+ assertAny('options', [is.object, is.undefined], options);
375
+ assertAny('defaults', [is.object, is.undefined], defaults);
306
376
  if (input instanceof Options || options instanceof Options) {
307
377
  throw new TypeError('The defaults must be passed as the third argument');
308
378
  }
@@ -386,6 +456,10 @@ export default class Options {
386
456
  if (key === 'url') {
387
457
  continue;
388
458
  }
459
+ // Never merge `preserveHooks` - it's a control flag, not a persistent option
460
+ if (key === 'preserveHooks') {
461
+ continue;
462
+ }
389
463
  if (!(key in this)) {
390
464
  throw new Error(`Unexpected option: ${key}`);
391
465
  }
@@ -416,7 +490,7 @@ export default class Options {
416
490
  return this._internals.request;
417
491
  }
418
492
  set request(value) {
419
- assert.any([is.function, is.undefined], value);
493
+ assertAny('request', [is.function, is.undefined], value);
420
494
  this._internals.request = value;
421
495
  }
422
496
  /**
@@ -445,14 +519,14 @@ export default class Options {
445
519
  return this._internals.agent;
446
520
  }
447
521
  set agent(value) {
448
- assert.plainObject(value);
522
+ assertPlainObject('agent', value);
449
523
  // eslint-disable-next-line guard-for-in
450
524
  for (const key in value) {
451
525
  if (!(key in this._internals.agent)) {
452
526
  throw new TypeError(`Unexpected agent option: ${key}`);
453
527
  }
454
528
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
455
- assert.any([is.object, is.undefined], value[key]);
529
+ assertAny(`agent.${key}`, [is.object, is.undefined, (v) => v === false], value[key]);
456
530
  }
457
531
  if (this._merging) {
458
532
  Object.assign(this._internals.agent, value);
@@ -505,14 +579,14 @@ export default class Options {
505
579
  return this._internals.timeout;
506
580
  }
507
581
  set timeout(value) {
508
- assert.plainObject(value);
582
+ assertPlainObject('timeout', value);
509
583
  // eslint-disable-next-line guard-for-in
510
584
  for (const key in value) {
511
585
  if (!(key in this._internals.timeout)) {
512
586
  throw new Error(`Unexpected timeout option: ${key}`);
513
587
  }
514
588
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
515
- assert.any([is.number, is.undefined], value[key]);
589
+ assertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);
516
590
  }
517
591
  if (this._merging) {
518
592
  Object.assign(this._internals.timeout, value);
@@ -566,7 +640,7 @@ export default class Options {
566
640
  return this._internals.prefixUrl;
567
641
  }
568
642
  set prefixUrl(value) {
569
- assert.any([is.string, is.urlInstance], value);
643
+ assertAny('prefixUrl', [is.string, is.urlInstance], value);
570
644
  if (value === '') {
571
645
  this._internals.prefixUrl = '';
572
646
  return;
@@ -590,15 +664,32 @@ export default class Options {
590
664
 
591
665
  __Note #4__: This option is not enumerable and will not be merged with the instance defaults.
592
666
 
593
- The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
667
+ The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / typed array ([`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), etc.) / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
594
668
 
595
669
  Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
670
+
671
+ You can use `Iterable` and `AsyncIterable` objects as request body, including Web [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream):
672
+
673
+ @example
674
+ ```
675
+ import got from 'got';
676
+
677
+ // Using an async generator
678
+ async function* generateData() {
679
+ yield 'Hello, ';
680
+ yield 'world!';
681
+ }
682
+
683
+ await got.post('https://httpbin.org/anything', {
684
+ body: generateData()
685
+ });
686
+ ```
596
687
  */
597
688
  get body() {
598
689
  return this._internals.body;
599
690
  }
600
691
  set body(value) {
601
- assert.any([is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, isFormData, is.undefined], value);
692
+ assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, isFormData, is.typedArray, is.undefined], value);
602
693
  if (is.nodeStream(value)) {
603
694
  assert.truthy(value.readable);
604
695
  }
@@ -621,7 +712,7 @@ export default class Options {
621
712
  return this._internals.form;
622
713
  }
623
714
  set form(value) {
624
- assert.any([is.plainObject, is.undefined], value);
715
+ assertAny('form', [is.plainObject, is.undefined], value);
625
716
  if (value !== undefined) {
626
717
  assert.undefined(this._internals.body);
627
718
  assert.undefined(this._internals.json);
@@ -629,7 +720,9 @@ export default class Options {
629
720
  this._internals.form = value;
630
721
  }
631
722
  /**
632
- JSON body. If the `Content-Type` header is not set, it will be set to `application/json`.
723
+ JSON request body. If the `content-type` header is not set, it will be set to `application/json`.
724
+
725
+ __Important__: This option only affects the request body you send to the server. To parse the response as JSON, you must either call `.json()` on the promise or set `responseType: 'json'` in the options.
633
726
 
634
727
  __Note #1__: If you provide this option, `got.stream()` will be read-only.
635
728
 
@@ -667,7 +760,7 @@ export default class Options {
667
760
  return this._internals.url;
668
761
  }
669
762
  set url(value) {
670
- assert.any([is.string, is.urlInstance, is.undefined], value);
763
+ assertAny('url', [is.string, is.urlInstance, is.undefined], value);
671
764
  if (value === undefined) {
672
765
  this._internals.url = undefined;
673
766
  return;
@@ -727,7 +820,7 @@ export default class Options {
727
820
  return this._internals.cookieJar;
728
821
  }
729
822
  set cookieJar(value) {
730
- assert.any([is.object, is.undefined], value);
823
+ assertAny('cookieJar', [is.object, is.undefined], value);
731
824
  if (value === undefined) {
732
825
  this._internals.cookieJar = undefined;
733
826
  return;
@@ -814,7 +907,7 @@ export default class Options {
814
907
  return this._internals.searchParams;
815
908
  }
816
909
  set searchParams(value) {
817
- assert.any([is.string, is.object, is.undefined], value);
910
+ assertAny('searchParams', [is.string, is.object, is.undefined], value);
818
911
  const url = this._internals.url;
819
912
  if (value === undefined) {
820
913
  this._internals.searchParams = undefined;
@@ -874,7 +967,7 @@ export default class Options {
874
967
  return this._internals.dnsLookup;
875
968
  }
876
969
  set dnsLookup(value) {
877
- assert.any([is.function, is.undefined], value);
970
+ assertAny('dnsLookup', [is.function, is.undefined], value);
878
971
  this._internals.dnsLookup = value;
879
972
  }
880
973
  /**
@@ -891,7 +984,7 @@ export default class Options {
891
984
  return this._internals.dnsCache;
892
985
  }
893
986
  set dnsCache(value) {
894
- assert.any([is.object, is.boolean, is.undefined], value);
987
+ assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
895
988
  if (value === true) {
896
989
  this._internals.dnsCache = getGlobalDnsCache();
897
990
  }
@@ -961,7 +1054,7 @@ export default class Options {
961
1054
  }
962
1055
  const typedKnownHookEvent = knownHookEvent;
963
1056
  const hooks = value[typedKnownHookEvent];
964
- assert.any([is.array, is.undefined], hooks);
1057
+ assertAny(`hooks.${knownHookEvent}`, [is.array, is.undefined], hooks);
965
1058
  if (hooks) {
966
1059
  for (const hook of hooks) {
967
1060
  assert.function(hook);
@@ -996,7 +1089,7 @@ export default class Options {
996
1089
  return this._internals.followRedirect;
997
1090
  }
998
1091
  set followRedirect(value) {
999
- assert.any([is.boolean, is.function], value);
1092
+ assertAny('followRedirect', [is.boolean, is.function], value);
1000
1093
  this._internals.followRedirect = value;
1001
1094
  }
1002
1095
  get followRedirects() {
@@ -1026,7 +1119,7 @@ export default class Options {
1026
1119
  return this._internals.cache;
1027
1120
  }
1028
1121
  set cache(value) {
1029
- assert.any([is.object, is.string, is.boolean, is.undefined], value);
1122
+ assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
1030
1123
  if (value === true) {
1031
1124
  this._internals.cache = globalCache;
1032
1125
  }
@@ -1034,7 +1127,7 @@ export default class Options {
1034
1127
  this._internals.cache = undefined;
1035
1128
  }
1036
1129
  else {
1037
- this._internals.cache = value;
1130
+ this._internals.cache = wrapQuickLruIfNeeded(value);
1038
1131
  }
1039
1132
  }
1040
1133
  /**
@@ -1139,7 +1232,7 @@ export default class Options {
1139
1232
  return this._internals.headers;
1140
1233
  }
1141
1234
  set headers(value) {
1142
- assert.plainObject(value);
1235
+ assertPlainObject('headers', value);
1143
1236
  if (this._merging) {
1144
1237
  Object.assign(this._internals.headers, lowercaseKeys(value));
1145
1238
  }
@@ -1261,7 +1354,9 @@ export default class Options {
1261
1354
  The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value.
1262
1355
  The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry).
1263
1356
 
1264
- __Note:__ When you provide `calculateDelay`, you take full control of retry decisions. The `limit` option is not automatically enforced - you must check `attemptCount` yourself or return `0` when `computedValue` is `0` to respect the default retry logic.
1357
+ The `enforceRetryRules` property is a `boolean` that, when set to `true`, enforces the `limit`, `methods`, `statusCodes`, and `errorCodes` options before calling `calculateDelay`. Your `calculateDelay` function is only invoked when a retry is allowed based on these criteria. When `false` (default), `calculateDelay` receives the computed value but can override all retry logic.
1358
+
1359
+ __Note:__ When `enforceRetryRules` is `false`, you must check `computedValue` in your `calculateDelay` function to respect the default retry logic. When `true`, the retry rules are enforced automatically.
1265
1360
 
1266
1361
  By default, it retries *only* on the specified methods, status codes, and on these network errors:
1267
1362
 
@@ -1281,14 +1376,15 @@ export default class Options {
1281
1376
  return this._internals.retry;
1282
1377
  }
1283
1378
  set retry(value) {
1284
- assert.plainObject(value);
1285
- assert.any([is.function, is.undefined], value.calculateDelay);
1286
- assert.any([is.number, is.undefined], value.maxRetryAfter);
1287
- assert.any([is.number, is.undefined], value.limit);
1288
- assert.any([is.array, is.undefined], value.methods);
1289
- assert.any([is.array, is.undefined], value.statusCodes);
1290
- assert.any([is.array, is.undefined], value.errorCodes);
1291
- assert.any([is.number, is.undefined], value.noise);
1379
+ assertPlainObject('retry', value);
1380
+ assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay);
1381
+ assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter);
1382
+ assertAny('retry.limit', [is.number, is.undefined], value.limit);
1383
+ assertAny('retry.methods', [is.array, is.undefined], value.methods);
1384
+ assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes);
1385
+ assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes);
1386
+ assertAny('retry.noise', [is.number, is.undefined], value.noise);
1387
+ assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules);
1292
1388
  if (value.noise && Math.abs(value.noise) > 100) {
1293
1389
  throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
1294
1390
  }
@@ -1317,7 +1413,7 @@ export default class Options {
1317
1413
  return this._internals.localAddress;
1318
1414
  }
1319
1415
  set localAddress(value) {
1320
- assert.any([is.string, is.undefined], value);
1416
+ assertAny('localAddress', [is.string, is.undefined], value);
1321
1417
  this._internals.localAddress = value;
1322
1418
  }
1323
1419
  /**
@@ -1336,7 +1432,7 @@ export default class Options {
1336
1432
  return this._internals.createConnection;
1337
1433
  }
1338
1434
  set createConnection(value) {
1339
- assert.any([is.function, is.undefined], value);
1435
+ assertAny('createConnection', [is.function, is.undefined], value);
1340
1436
  this._internals.createConnection = value;
1341
1437
  }
1342
1438
  /**
@@ -1348,11 +1444,11 @@ export default class Options {
1348
1444
  return this._internals.cacheOptions;
1349
1445
  }
1350
1446
  set cacheOptions(value) {
1351
- assert.plainObject(value);
1352
- assert.any([is.boolean, is.undefined], value.shared);
1353
- assert.any([is.number, is.undefined], value.cacheHeuristic);
1354
- assert.any([is.number, is.undefined], value.immutableMinTimeToLive);
1355
- assert.any([is.boolean, is.undefined], value.ignoreCargoCult);
1447
+ assertPlainObject('cacheOptions', value);
1448
+ assertAny('cacheOptions.shared', [is.boolean, is.undefined], value.shared);
1449
+ assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
1450
+ assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
1451
+ assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
1356
1452
  for (const key in value) {
1357
1453
  if (!(key in this._internals.cacheOptions)) {
1358
1454
  throw new Error(`Cache option \`${key}\` does not exist`);
@@ -1372,24 +1468,26 @@ export default class Options {
1372
1468
  return this._internals.https;
1373
1469
  }
1374
1470
  set https(value) {
1375
- assert.plainObject(value);
1376
- assert.any([is.boolean, is.undefined], value.rejectUnauthorized);
1377
- assert.any([is.function, is.undefined], value.checkServerIdentity);
1378
- assert.any([is.string, is.object, is.array, is.undefined], value.certificateAuthority);
1379
- assert.any([is.string, is.object, is.array, is.undefined], value.key);
1380
- assert.any([is.string, is.object, is.array, is.undefined], value.certificate);
1381
- assert.any([is.string, is.undefined], value.passphrase);
1382
- assert.any([is.string, is.buffer, is.array, is.undefined], value.pfx);
1383
- assert.any([is.array, is.undefined], value.alpnProtocols);
1384
- assert.any([is.string, is.undefined], value.ciphers);
1385
- assert.any([is.string, is.buffer, is.undefined], value.dhparam);
1386
- assert.any([is.string, is.undefined], value.signatureAlgorithms);
1387
- assert.any([is.string, is.undefined], value.minVersion);
1388
- assert.any([is.string, is.undefined], value.maxVersion);
1389
- assert.any([is.boolean, is.undefined], value.honorCipherOrder);
1390
- assert.any([is.number, is.undefined], value.tlsSessionLifetime);
1391
- assert.any([is.string, is.undefined], value.ecdhCurve);
1392
- assert.any([is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1471
+ assertPlainObject('https', value);
1472
+ assertAny('https.rejectUnauthorized', [is.boolean, is.undefined], value.rejectUnauthorized);
1473
+ assertAny('https.checkServerIdentity', [is.function, is.undefined], value.checkServerIdentity);
1474
+ assertAny('https.serverName', [is.string, is.undefined], value.serverName);
1475
+ assertAny('https.certificateAuthority', [is.string, is.object, is.array, is.undefined], value.certificateAuthority);
1476
+ assertAny('https.key', [is.string, is.object, is.array, is.undefined], value.key);
1477
+ assertAny('https.certificate', [is.string, is.object, is.array, is.undefined], value.certificate);
1478
+ assertAny('https.passphrase', [is.string, is.undefined], value.passphrase);
1479
+ assertAny('https.pfx', [is.string, is.buffer, is.array, is.undefined], value.pfx);
1480
+ assertAny('https.alpnProtocols', [is.array, is.undefined], value.alpnProtocols);
1481
+ assertAny('https.ciphers', [is.string, is.undefined], value.ciphers);
1482
+ assertAny('https.dhparam', [is.string, is.buffer, is.undefined], value.dhparam);
1483
+ assertAny('https.signatureAlgorithms', [is.string, is.undefined], value.signatureAlgorithms);
1484
+ assertAny('https.minVersion', [is.string, is.undefined], value.minVersion);
1485
+ assertAny('https.maxVersion', [is.string, is.undefined], value.maxVersion);
1486
+ assertAny('https.honorCipherOrder', [is.boolean, is.undefined], value.honorCipherOrder);
1487
+ assertAny('https.tlsSessionLifetime', [is.number, is.undefined], value.tlsSessionLifetime);
1488
+ assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
1489
+ assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1490
+ assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
1393
1491
  for (const key in value) {
1394
1492
  if (!(key in this._internals.https)) {
1395
1493
  throw new Error(`HTTPS option \`${key}\` does not exist`);
@@ -1419,7 +1517,7 @@ export default class Options {
1419
1517
  if (value === null) {
1420
1518
  throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
1421
1519
  }
1422
- assert.any([is.string, is.undefined], value);
1520
+ assertAny('encoding', [is.string, is.undefined], value);
1423
1521
  this._internals.encoding = value;
1424
1522
  }
1425
1523
  /**
@@ -1519,7 +1617,7 @@ export default class Options {
1519
1617
  return this._internals.maxHeaderSize;
1520
1618
  }
1521
1619
  set maxHeaderSize(value) {
1522
- assert.any([is.number, is.undefined], value);
1620
+ assertAny('maxHeaderSize', [is.number, is.undefined], value);
1523
1621
  this._internals.maxHeaderSize = value;
1524
1622
  }
1525
1623
  get enableUnixSockets() {
@@ -1529,6 +1627,23 @@ export default class Options {
1529
1627
  assert.boolean(value);
1530
1628
  this._internals.enableUnixSockets = value;
1531
1629
  }
1630
+ /**
1631
+ Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
1632
+
1633
+ This is useful for detecting truncated responses and follows RFC 9112 requirements for message completeness.
1634
+
1635
+ __Note__: Responses without a `content-length` header are not validated.
1636
+ __Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
1637
+
1638
+ @default false
1639
+ */
1640
+ get strictContentLength() {
1641
+ return this._internals.strictContentLength;
1642
+ }
1643
+ set strictContentLength(value) {
1644
+ assert.boolean(value);
1645
+ this._internals.strictContentLength = value;
1646
+ }
1532
1647
  // eslint-disable-next-line @typescript-eslint/naming-convention
1533
1648
  toJSON() {
1534
1649
  return { ...this._internals };
@@ -1541,7 +1656,17 @@ export default class Options {
1541
1656
  const url = internals.url;
1542
1657
  let agent;
1543
1658
  if (url.protocol === 'https:') {
1544
- agent = internals.http2 ? internals.agent : internals.agent.https;
1659
+ if (internals.http2) {
1660
+ // Ensure HTTP/2 agent is configured for connection reuse
1661
+ // If no custom agent.http2 is provided, use the global agent for connection pooling
1662
+ agent = {
1663
+ ...internals.agent,
1664
+ http2: internals.agent.http2 ?? http2wrapper.globalAgent,
1665
+ };
1666
+ }
1667
+ else {
1668
+ agent = internals.agent.https;
1669
+ }
1545
1670
  }
1546
1671
  else {
1547
1672
  agent = internals.agent.http;
@@ -1567,6 +1692,7 @@ export default class Options {
1567
1692
  pfx: https.pfx,
1568
1693
  rejectUnauthorized: https.rejectUnauthorized,
1569
1694
  checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity,
1695
+ servername: https.serverName,
1570
1696
  ciphers: https.ciphers,
1571
1697
  honorCipherOrder: https.honorCipherOrder,
1572
1698
  minVersion: https.minVersion,
@@ -1576,6 +1702,7 @@ export default class Options {
1576
1702
  dhparam: https.dhparam,
1577
1703
  ecdhCurve: https.ecdhCurve,
1578
1704
  crl: https.certificateRevocationLists,
1705
+ secureOptions: https.secureOptions,
1579
1706
  // HTTP options
1580
1707
  lookup: internals.dnsLookup ?? internals.dnsCache?.lookup,
1581
1708
  family: internals.dnsLookupIpVersion,
@@ -15,8 +15,24 @@ export default async function getBodySize(body, headers) {
15
15
  if (is.buffer(body)) {
16
16
  return body.length;
17
17
  }
18
+ if (is.typedArray(body)) {
19
+ return body.byteLength;
20
+ }
18
21
  if (isFormData(body)) {
19
- return promisify(body.getLength.bind(body))();
22
+ try {
23
+ return await promisify(body.getLength.bind(body))();
24
+ }
25
+ catch (error) {
26
+ const typedError = error;
27
+ throw new Error('Cannot determine content-length for form-data with stream(s) of unknown length. '
28
+ + 'This is a limitation of the `form-data` package. '
29
+ + 'To fix this, either:\n'
30
+ + '1. Use the `knownLength` option when appending streams:\n'
31
+ + ' form.append(\'file\', stream, {knownLength: 12345});\n'
32
+ + '2. Switch to spec-compliant FormData (formdata-node package)\n'
33
+ + 'See: https://github.com/form-data/form-data#alternative-submission-methods\n'
34
+ + `Original error: ${typedError.message}`);
35
+ }
20
36
  }
21
37
  return undefined;
22
38
  }
@@ -1 +1,17 @@
1
1
  export default function isUnixSocketURL(url: URL): boolean;
2
+ /**
3
+ Extract the socket path from a UNIX socket URL.
4
+
5
+ @example
6
+ ```
7
+ getUnixSocketPath(new URL('http://unix/foo:/path'));
8
+ //=> '/foo'
9
+
10
+ getUnixSocketPath(new URL('unix:/foo:/path'));
11
+ //=> '/foo'
12
+
13
+ getUnixSocketPath(new URL('http://example.com'));
14
+ //=> undefined
15
+ ```
16
+ */
17
+ export declare function getUnixSocketPath(url: URL): string | undefined;
@@ -2,3 +2,24 @@
2
2
  export default function isUnixSocketURL(url) {
3
3
  return url.protocol === 'unix:' || url.hostname === 'unix';
4
4
  }
5
+ /**
6
+ Extract the socket path from a UNIX socket URL.
7
+
8
+ @example
9
+ ```
10
+ getUnixSocketPath(new URL('http://unix/foo:/path'));
11
+ //=> '/foo'
12
+
13
+ getUnixSocketPath(new URL('unix:/foo:/path'));
14
+ //=> '/foo'
15
+
16
+ getUnixSocketPath(new URL('http://example.com'));
17
+ //=> undefined
18
+ ```
19
+ */
20
+ export function getUnixSocketPath(url) {
21
+ if (!isUnixSocketURL(url)) {
22
+ return undefined;
23
+ }
24
+ return /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`)?.groups?.socketPath;
25
+ }
@@ -139,7 +139,15 @@ const create = (defaults) => {
139
139
  }
140
140
  else {
141
141
  normalizedOptions.merge(optionsToMerge);
142
- assert.any([is.urlInstance, is.undefined], optionsToMerge.url);
142
+ try {
143
+ assert.any([is.urlInstance, is.undefined], optionsToMerge.url);
144
+ }
145
+ catch (error) {
146
+ if (error instanceof Error) {
147
+ error.message = `Option 'pagination.paginate.url': ${error.message}`;
148
+ }
149
+ throw error;
150
+ }
143
151
  if (optionsToMerge.url !== undefined) {
144
152
  normalizedOptions.prefixUrl = '';
145
153
  normalizedOptions.url = optionsToMerge.url;