Repository: sindresorhus/got
Pull request #645 (2018-10-26 18:11:30)

Allow defaults to be mutable


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..0655cb5 100644
--- a/readme.md
+++ b/readme.md
@@ -388,23 +388,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})