From 0a6beb1327e0c0be51f515b79b9c09f455601b75 Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Fri, 26 Apr 2019 08:22:18 +0200 Subject: [PATCH] Fix `ky.extend()` not inheriting its parent and add `ky.create()` (#128) --- index.d.ts | 9 +++++++++ index.js | 19 +++++++++++++------ index.test-d.ts | 1 + readme.md | 8 +++++++- test/main.js | 50 ++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/index.d.ts b/index.d.ts index 5296a2d8..a3f06220 100644 --- a/index.d.ts +++ b/index.d.ts @@ -211,9 +211,18 @@ declare const ky: { */ delete(url: Request | URL | string, options?: Options): ResponsePromise; + /** + Create a new Ky instance with complete new defaults. + + @returns A new Ky instance. + */ + create(defaultOptions: Options): typeof ky; + /** Create a new Ky instance with some defaults overridden with your own. + In contrast to `ky.create()`, `ky.extend()` inherits defaults from its parent. + @returns A new Ky instance. */ extend(defaultOptions: Options): typeof ky; diff --git a/index.js b/index.js index ce286d4b..5ba083b2 100644 --- a/index.js +++ b/index.js @@ -313,18 +313,25 @@ class Ky { } } -const createInstance = (defaults = {}) => { - if (!isObject(defaults) || Array.isArray(defaults)) { - throw new TypeError('The `defaultOptions` argument must be an object'); +const validateAndMerge = (...sources) => { + for (const source of sources) { + if ((!isObject(source) || Array.isArray(source)) && typeof source !== 'undefined') { + throw new TypeError('The `options` argument must be an object'); + } } - const ky = (input, options) => new Ky(input, deepMerge({}, defaults, options)); + return deepMerge({}, ...sources); +}; + +const createInstance = defaults => { + const ky = (input, options) => new Ky(input, validateAndMerge(defaults, options)); for (const method of requestMethods) { - ky[method] = (input, options) => new Ky(input, deepMerge({}, defaults, options, {method})); + ky[method] = (input, options) => new Ky(input, validateAndMerge(defaults, options, {method})); } - ky.extend = defaults => createInstance(defaults); + ky.create = newDefaults => createInstance(validateAndMerge(newDefaults)); + ky.extend = newDefaults => createInstance(validateAndMerge(defaults, newDefaults)); return ky; }; diff --git a/index.test-d.ts b/index.test-d.ts index 5cfb3313..3cc39061 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -35,6 +35,7 @@ for (const method of requestBodyMethods) { expectType(ky[method as RequestBodyMethod](url, {body: 'x'})); } +expectType(ky.create({})); expectType(ky.extend({})); expectType(new HTTPError(new Response)); expectType(new TimeoutError); diff --git a/readme.md b/readme.md index 6b575a5d..92b5d351 100644 --- a/readme.md +++ b/readme.md @@ -246,12 +246,18 @@ Setting this to `false` may be useful if you are checking for resource availabil Create a new `ky` instance with some defaults overridden with your own. +In contrast to `ky.create()`, `ky.extend()` inherits defaults from its parent. + +### ky.create(defaultOptions) + +Create a new Ky instance with complete new defaults. + ```js import ky from 'ky'; // On https://my-site.com -const api = ky.extend({prefixUrl: 'https://example.com/api'}); +const api = ky.create({prefixUrl: 'https://example.com/api'}); (async () => { await api.get('users/123'); diff --git a/test/main.js b/test/main.js index ed3a017c..22c532c0 100644 --- a/test/main.js +++ b/test/main.js @@ -258,13 +258,13 @@ test('throwHttpErrors option with POST', async t => { await server.close(); }); -test('ky.extend()', async t => { +test('ky.create()', async t => { const server = await createTestServer(); server.get('/', (request, response) => { response.end(`${request.headers.unicorn} - ${request.headers.rainbow}`); }); - const extended = ky.extend({ + const extended = ky.create({ headers: { rainbow: 'rainbow' } @@ -284,7 +284,7 @@ test('ky.extend()', async t => { await server.close(); }); -test('ky.extend() throws when given non-object argument', t => { +test('ky.create() throws when given non-object argument', t => { const nonObjectValues = [ true, 666, @@ -297,15 +297,15 @@ test('ky.extend() throws when given non-object argument', t => { for (const value of nonObjectValues) { t.throws(() => { - ky.extend(value); + ky.create(value); }, { instanceOf: TypeError, - message: 'The `defaultOptions` argument must be an object' + message: 'The `options` argument must be an object' }); } }); -test('ky.extend() with deep array', async t => { +test('ky.create() with deep array', async t => { const server = await createTestServer(); server.get('/', (request, response) => { response.end(); @@ -314,7 +314,7 @@ test('ky.extend() with deep array', async t => { let isOriginBeforeRequestTrigged = false; let isExtendBeforeRequestTrigged = false; - const extended = ky.extend({ + const extended = ky.create({ hooks: { beforeRequest: [ () => { @@ -341,6 +341,42 @@ test('ky.extend() with deep array', async t => { await server.close(); }); +test('ky.extend()', async t => { + const server = await createTestServer(); + server.get('/', (request, response) => { + response.end(); + }); + + let isOriginBeforeRequestTrigged = false; + let isExtendBeforeRequestTrigged = false; + + const extended = ky.extend({ + hooks: { + beforeRequest: [ + () => { + isOriginBeforeRequestTrigged = true; + } + ] + } + }).extend({ + hooks: { + beforeRequest: [ + () => { + isExtendBeforeRequestTrigged = true; + } + ] + } + }); + + await extended(server.url); + + t.is(isOriginBeforeRequestTrigged, true); + t.is(isExtendBeforeRequestTrigged, true); + t.true((await extended.head(server.url)).ok); + + await server.close(); +}); + test('throws AbortError when aborted by user', async t => { const server = await createTestServer(); server.get('/', () => {});