Repository: sindresorhus/got (ts)

Issue:
Arrays in query properties
## Problem

The documentation says that [`query`](https://github.com/sindresorhus/got#query) property can be represented as an object.

If the property is an array, the request path is built incorrectly.

```js
const res = await got('https://site.com', {
  query: {
    some: ['a', 'b']
  }
});
```

## Current behaviour

```
request => https://site.com/?some=a%2Cb
```

## Expected behaviour

```
request => https://site.com/?some=a&some=b
```

## Problem research

In the `got@8` we use `querystring`.

https://github.com/sindresorhus/got/blob/ad7b361dcb2490c3864b845b979b756f13f7d89b/index.js#L552

In the `got@9` we use `URLSearchParams`.

https://github.com/sindresorhus/got/blob/8d2e91171509bc2aec71c4483978cfd421ebcf1d/source/normalize-arguments.js#L118

```js
// Current behaviour
new URL.URLSearchParams({ some: ['a', 'b'] }).toString() // -> some=a%2Cb

// Correct way
new URL.URLSearchParams([["some", 'a'],["some", 'b']]).toString() // -> some=a&some=b
```

If I understand the description correctly, I cannot pass an object to this constructor:

https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams

Pull request:
Improve docs for the `query` option
Fixes #638
Closes #641

Patch:
diff --git a/advanced-creation.md b/advanced-creation.md
index da9377d..349734c 100644
--- a/advanced-creation.md
+++ b/advanced-creation.md
@@ -14,6 +14,13 @@ Configure a new `got` instance with the provided settings.<br>
 To inherit from parent, set it as `got.defaults.options` or use [`got.mergeOptions(defaults.options, options)`](readme.md#gotmergeoptionsparentoptions-newoptions).<br>
 **Note**: Avoid using [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) as it doesn't work recursively.
 
+##### mutableDefaults
+
+Type: `boolean`<br>
+Default: `false`
+
+States if the defaults are mutable. It's very useful when you need to [update headers over time](readme.md#hooksafterresponse).
+
 ##### handler
 
 Type: `Function`<br>
diff --git a/readme.md b/readme.md
index fd414bc..a973f54 100644
--- a/readme.md
+++ b/readme.md
@@ -203,9 +203,36 @@ Parse response body with `JSON.parse` and set `accept` header to `application/js
 
 ###### query
 
-Type: `string` `Object` [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
+Type: `string` `Object<string, string|number>` [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
 
-Query string object that will be added to the request URL. This will override the query string in `url`.
+Query string that will be added to the request URL. This will override the query string in `url`.
+
+If you need to pass in an array, you can do it using a `URLSearchParams` instance:
+
+```js
+const got = require('got');
+
+const query = new URLSearchParams([['key', 'a'], ['key', 'b']]);
+
+got('https://example.com', {query});
+
+console.log(query.toString());
+//=> 'key=a&key=b'
+```
+
+And if you need a different array format, you could use the [`query-string`](https://github.com/sindresorhus/query-string) package:
+
+```js
+const got = require('got');
+const queryString = require('query-string');
+
+const query = queryString.stringify({key: ['a', 'b']}, {arrayFormat: 'bracket'});
+
+got('https://example.com', {query});
+
+console.log(query);
+//=> 'key[]=a&key[]=b'
+```
 
 ###### timeout
 
@@ -388,23 +415,30 @@ Called with [response object](#response). Each function should return the respon
 ```js
 const got = require('got');
 
-got('example.com', {
+const instance = got.extend({
 	hooks: {
 		afterResponse: [
 			response => {
 				if (response.statusCode === 401) { // Unauthorized
-					return {
+					const updatedOptions = {
 						headers: {
-							token: getNewToken(); // Refresh the access token
+							token: getNewToken() // Refresh the access token
 						}
 					};
+
+					// Save for further requests
+					instance.defaults.options = got.mergeOptions(instance.defaults.options, updatedOptions);
+
+					// Apply changes
+					return updatedOptions;
 				}
 
 				// No changes otherwise
 				return response;
 			}
 		]
-	}
+	},
+	mutableDefaults: true
 });
 ```
 
diff --git a/source/create.js b/source/create.js
index 1f6e6d7..b78c51f 100644
--- a/source/create.js
+++ b/source/create.js
@@ -40,10 +40,21 @@ const create = defaults => {
 	}
 
 	got.create = create;
-	got.extend = options => create({
-		options: merge.options(defaults.options, options),
-		handler: defaults.handler
-	});
+	got.extend = options => {
+		let mutableDefaults;
+		if (options && Reflect.has(options, 'mutableDefaults')) {
+			mutableDefaults = options.mutableDefaults;
+			delete options.mutableDefaults;
+		} else {
+			mutableDefaults = defaults.mutableDefaults;
+		}
+
+		return create({
+			options: merge.options(defaults.options, options),
+			handler: defaults.handler,
+			mutableDefaults
+		});
+	};
 
 	got.mergeInstances = (...args) => create(merge.instances(args));
 
@@ -56,10 +67,10 @@ const create = defaults => {
 
 	Object.assign(got, {...errors, mergeOptions: merge.options});
 	Object.defineProperty(got, 'defaults', {
-		value: deepFreeze(defaults),
-		writable: false,
-		enumerable: true,
-		configurable: true
+		value: defaults.mutableDefaults ? defaults : deepFreeze(defaults),
+		writable: defaults.mutableDefaults,
+		configurable: defaults.mutableDefaults,
+		enumerable: true
 	});
 
 	return got;
diff --git a/source/index.js b/source/index.js
index a8f49aa..e835998 100644
--- a/source/index.js
+++ b/source/index.js
@@ -41,7 +41,8 @@ const defaults = {
 		json: false,
 		cache: false,
 		useElectronNet: false
-	}
+	},
+	mutableDefaults: false
 };
 
 const got = create(defaults);


Test patch:
diff --git a/test/create.js b/test/create.js
index 80d8e4a..428f37e 100644
--- a/test/create.js
+++ b/test/create.js
@@ -156,6 +156,34 @@ test('no tampering with defaults', t => {
 	t.is(instance2.defaults.options.baseUrl, 'example/');
 });
 
+test('defaults can be mutable', t => {
+	const instance = got.create({
+		mutableDefaults: true,
+		options: {
+			followRedirect: false
+		}
+	});
+
+	t.notThrows(() => {
+		instance.defaults.options.followRedirect = true;
+	});
+
+	t.true(instance.defaults.options.followRedirect);
+});
+
+test('can set mutable defaults using got.extend', t => {
+	const instance = got.extend({
+		mutableDefaults: true,
+		followRedirect: false
+	});
+
+	t.notThrows(() => {
+		instance.defaults.options.followRedirect = true;
+	});
+
+	t.true(instance.defaults.options.followRedirect);
+});
+
 test('only plain objects are freezed', async t => {
 	const instance = got.extend({
 		agent: new http.Agent({keepAlive: true})