Skip to content

Commit

Permalink
Fix ky.extend() not inheriting its parent and add ky.create() (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak authored and sindresorhus committed Apr 26, 2019
1 parent 813d28b commit 0a6beb1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
9 changes: 9 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 13 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ for (const method of requestBodyMethods) {
expectType<ResponsePromise>(ky[method as RequestBodyMethod](url, {body: 'x'}));
}

expectType<typeof ky>(ky.create({}));
expectType<typeof ky>(ky.extend({}));
expectType<HTTPError>(new HTTPError(new Response));
expectType<TimeoutError>(new TimeoutError);
Expand Down
8 changes: 7 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
50 changes: 43 additions & 7 deletions test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand All @@ -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,
Expand All @@ -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();
Expand All @@ -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: [
() => {
Expand All @@ -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('/', () => {});
Expand Down

0 comments on commit 0a6beb1

Please sign in to comment.