Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passing a function to ky.extend() #611

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,22 @@ console.log('unicorn' in response);
//=> true
```

You can also refer to parent defaults by providing a function to `.extend()`.

```js
import ky from 'ky';

const api = ky.create({prefixUrl: 'https://example.com/api'});

const usersApi = api.extend((options) => ({prefixUrl: `${options.prefixUrl}/users`}));

const response = await usersApi.get('123');
//=> 'https://example.com/api/users/123'

const response = await api.get('version');
//=> 'https://example.com/api/version'
```
mfulton26 marked this conversation as resolved.
Show resolved Hide resolved

### ky.create(defaultOptions)

Create a new Ky instance with complete new defaults.
Expand Down
9 changes: 8 additions & 1 deletion source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ const createInstance = (defaults?: Partial<Options>): KyInstance => {
}

ky.create = (newDefaults?: Partial<Options>) => createInstance(validateAndMerge(newDefaults));
ky.extend = (newDefaults?: Partial<Options>) => createInstance(validateAndMerge(defaults, newDefaults));
ky.extend = (newDefaults?: Partial<Options> | ((parentDefaults: Partial<Options>) => Partial<Options>)) => {
if (typeof newDefaults === 'function') {
newDefaults = newDefaults(defaults ?? {});
}

return createInstance(validateAndMerge(defaults, newDefaults));
};

ky.stop = stop;

return ky as KyInstance;
Expand Down
2 changes: 1 addition & 1 deletion source/types/ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export type KyInstance = {

@returns A new Ky instance.
*/
extend: (defaultOptions: Options) => KyInstance;
extend: (defaultOptions: Options | ((parentOptions: Options) => Options)) => KyInstance;

/**
A `Symbol` that can be returned by a `beforeRetry` hook to stop the retry. This will also short circuit the remaining `beforeRetry` hooks.
Expand Down
95 changes: 76 additions & 19 deletions test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ test('ky.create() does not mangle search params', async t => {
await server.close();
});

test('ky.extend()', async t => {
const extendHooksMacro = test.macro<[{useFunction: boolean}]>(async (t, {useFunction}) => {
const server = await createHttpTestServer();
server.get('/', (_request, response) => {
response.end();
Expand All @@ -551,25 +551,28 @@ test('ky.extend()', async t => {
let isOriginBeforeRequestTrigged = false;
let isExtendBeforeRequestTrigged = false;

const intermediateOptions = {
hooks: {
beforeRequest: [
() => {
isOriginBeforeRequestTrigged = true;
},
],
},
};
const extendedOptions = {
hooks: {
beforeRequest: [
() => {
isExtendBeforeRequestTrigged = true;
},
],
},
};

const extended = ky
.extend({
hooks: {
beforeRequest: [
() => {
isOriginBeforeRequestTrigged = true;
},
],
},
})
.extend({
hooks: {
beforeRequest: [
() => {
isExtendBeforeRequestTrigged = true;
},
],
},
});
.extend(useFunction ? () => intermediateOptions : intermediateOptions)
.extend(useFunction ? () => extendedOptions : extendedOptions);

await extended(server.url);

Expand All @@ -582,6 +585,60 @@ test('ky.extend()', async t => {
await server.close();
});

test('ky.extend() appends hooks', extendHooksMacro, {useFunction: false});

test('ky.extend() with function appends hooks', extendHooksMacro, {useFunction: false});

test('ky.extend() with function overrides primitives in parent defaults', async t => {
const server = await createHttpTestServer();
server.get('*', (request, response) => {
response.end(request.url);
});

const api = ky.create({prefixUrl: `${server.url}/api`});
const usersApi = api.extend(options => ({prefixUrl: `${options.prefixUrl!.toString()}/users`}));

t.is(await usersApi.get('123').text(), '/api/users/123');
t.is(await api.get('version').text(), '/api/version');

{
const {ok} = await api.head(server.url);
t.true(ok);
}

{
const {ok} = await usersApi.head(server.url);
t.true(ok);
}

await server.close();
});

test('ky.extend() with function retains parent defaults when not specified', async t => {
const server = await createHttpTestServer();
server.get('*', (request, response) => {
response.end(request.url);
});

const api = ky.create({prefixUrl: `${server.url}/api`});
const extendedApi = api.extend(() => ({}));

t.is(await api.get('version').text(), '/api/version');
t.is(await extendedApi.get('something').text(), '/api/something');

{
const {ok} = await api.head(server.url);
t.true(ok);
}

{
const {ok} = await extendedApi.head(server.url);
t.true(ok);
}

await server.close();
});

test('throws DOMException/Error with name AbortError when aborted by user', async t => {
const server = await createHttpTestServer();
// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down