diff --git a/readme.md b/readme.md index 4eab6b14d..6ec61dfa2 100644 --- a/readme.md +++ b/readme.md @@ -251,9 +251,7 @@ JSON body. If the `Content-Type` header is not set, it will be set to `applicati Type: `object` -User data. In contrast to other options, `context` is not enumerable. - -**Note:** The object is never merged, it's just passed through. Got will not modify the object in any way. +User data. `context` is shallow merged. It's very useful for storing auth tokens: diff --git a/source/core/index.ts b/source/core/index.ts index 9b1821140..31c816366 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -515,10 +515,7 @@ interface PlainOptions extends URLOptions { dnsCache?: CacheableLookup | boolean; /** - User data. In contrast to other options, `context` is not enumerable. - - __Note__: The object is never merged, it's just passed through. - Got will not modify the object in any way. + User data. `context` is shallow merged. @example ``` @@ -1138,9 +1135,8 @@ const waitForOpenFile = async (file: ReadStream): Promise => new Promise(( const redirectCodes: ReadonlySet = new Set([300, 301, 302, 303, 304, 307, 308]); -type NonEnumerableProperty = 'context' | 'body' | 'json' | 'form'; +type NonEnumerableProperty = 'body' | 'json' | 'form'; const nonEnumerableProperties: NonEnumerableProperty[] = [ - 'context', 'body', 'json', 'form' @@ -1764,9 +1760,7 @@ export default class Request extends Duplex implements RequestEvents { } // `options.context` - if (!options.context) { - options.context = {}; - } + options.context = {...defaults?.context, ...options.context}; // `options.hooks` const areHooksDefault = options.hooks === defaults?.hooks; diff --git a/source/types.ts b/source/types.ts index 1cf29c378..d78866740 100644 --- a/source/types.ts +++ b/source/types.ts @@ -371,7 +371,7 @@ export interface Got extends Record, GotRequestFu - If the parent property is a plain `object` too, both values are merged recursively into a new `object`. - Otherwise, only the new value is deeply cloned. - If the new property is an `Array`, it overwrites the old one with a deep clone of the new property. - - Properties that are not enumerable, such as `context`, `body`, `json`, and `form`, will not be merged. + - Properties that are not enumerable, such as `body`, `json`, and `form`, will not be merged. - Otherwise, the new value is assigned to the key. **Note:** Only Got options are merged! Custom user options should be defined via [`options.context`](#context). diff --git a/test/arguments.ts b/test/arguments.ts index 5088b898a..db3aca724 100644 --- a/test/arguments.ts +++ b/test/arguments.ts @@ -328,7 +328,7 @@ test('throws if the `searchParams` value is invalid', async t => { }); }); -test('`context` option is not enumerable', withServer, async (t, server, got) => { +test('`context` option is enumerable', withServer, async (t, server, got) => { server.get('/', echoUrl); const context = { @@ -340,8 +340,8 @@ test('`context` option is not enumerable', withServer, async (t, server, got) => hooks: { beforeRequest: [ options => { - t.is(options.context, context); - t.false({}.propertyIsEnumerable.call(options, 'context')); + t.deepEqual(options.context, context); + t.true({}.propertyIsEnumerable.call(options, 'context')); } ] } @@ -360,8 +360,8 @@ test('`context` option is accessible when using hooks', withServer, async (t, se hooks: { beforeRequest: [ options => { - t.is(options.context, context); - t.false({}.propertyIsEnumerable.call(options, 'context')); + t.deepEqual(options.context, context); + t.true({}.propertyIsEnumerable.call(options, 'context')); } ] } @@ -375,8 +375,23 @@ test('`context` option is accessible when extending instances', t => { const instance = got.extend({context}); - t.is(instance.defaults.options.context, context); - t.false({}.propertyIsEnumerable.call(instance.defaults.options, 'context')); + t.deepEqual(instance.defaults.options.context, context); + t.true({}.propertyIsEnumerable.call(instance.defaults.options, 'context')); +}); + +test('`context` option is shallow merged', t => { + const context = { + foo: 'bar' + }; + + const instance1 = got.extend({context}); + + t.deepEqual(instance1.defaults.options.context, context); + t.true({}.propertyIsEnumerable.call(instance1.defaults.options, 'context')); + + const instance2 = instance1.extend({context: {}}); + + t.deepEqual(instance2.defaults.options.context, context); }); test('throws if `options.encoding` is `null`', async t => {