-
-
Notifications
You must be signed in to change notification settings - Fork 400
Add baseUrl option, rename prefix, and allow leading slashes in input #606
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
base: main
Are you sure you want to change the base?
Conversation
i see that it's going to be a breaking change, maybe it's worth about discusssing about the next major? i think it's good to remove default timeout and retry settings, because many people come from fetch and axios and it's not obvious ky have these defaults. |
I would be okay with removing the default timeout, but not the default retries. |
baseUrl looks like what I want. I only care about the URL's origin, and also need the ability to pass full URLs. If it works like this, it's perfect: ky.get('/api/v1/instance', { baseUrl : 'https://ditto.pub' }); // GET https://ditto.pub/api/v1/instance
ky.get('https://ditto.pub/api/v1/timelines/home?max_id=1234', { baseUrl : 'https://ditto.pub' }); // GET https://ditto.pub/api/v1/timelines/home?max_id=1234 In this case, I don't care about a path in the baseUrl. I assume it works like this: ky.get('/api/v1/instance', { baseUrl : 'https://ditto.pub/@alex' }); // GET https://ditto.pub/api/v1/instance But I will never pass a baseUrl with a path anyway. |
@alexgleason correct, each of those examples behaves as you described. I would expect most people to structure their requests like this: const api = ky.extend({ baseUrl : 'https://ditto.pub/api/v1/' });
api.get('instance'); // GET https://ditto.pub/api/v1/instance
api.get('timelines/home?max_id=1234', { baseUrl : 'https://ditto.pub/api/v1' }); // GET https://ditto.pub/api/v1/timelines/home?max_id=1234
api.get('https://example.com'); // GET https://example.com
api.get('/api/v2/secret'); // GET https://ditto.pub/api/v2/secret But you could certainly put the |
@sholladay I think the way to overcome code complexity and confusion while solving all use-cases is to just support a resolver function like mentioned here: #291 (comment) But I think it should actually look like this: const api = ky.create({
// Accept any `Input` and return any `Input`. Flexible and pure.
resolver(input: Input): Input {
// It's not actually complicated to do this yourself either.
const url = input instanceof Request ? input.url : input.toString();
return new Request(new URL(url, 'https://ditto.pub'), input);
}
}); We can keep the const api = ky.create({
prefixUrl: 'https://ditto.pub', // WARNING: `prefixUrl` is deprecated.
resolver(input: Input): Input { // ERROR: `prefixUrl` and `resolver` cannot be specified at the same time.
// ...
}
}); For improved DX, Ky can include resolvers for a prefix and baseUrl. import ky from 'ky';
const api = ky.create({
resolver: ky.prefix('https://ditto.pub/api/v1')
});
const api = ky.create({
resolver: ky.baseUrl('https://ditto.pub')
}); |
@alexgleason the way I am planning to solve that is by allowing users to return a URL instance in a |
@sholladay That could work as long as we have the |
Can it even get to the point of It's fine as a hook, but I think it might still have to be a separate hook. |
While it doesn't provide input, you can use |
@sholladay Currently we can already do that with |
What's the use case that causes you to need a base URL that is unknown at the time of creating the Ky instance? In other words, why is it dynamic? |
@sholladay Supporting multiple accounts on Mastodon. |
I ended up just writing a custom bare minimum fetch wrapper and accepting that I will have to do |
@bertho-zero you didn't explain what you prefer about this PR and why. Your regex is shorter but the current code is more readable and should have slightly better performance. Both are fine, though, and I don't have strong feelings about it. |
source/types/options.ts
Outdated
- After `prefixUrl` and `input` are joined, the result is resolved against the [base URL](https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI) of the page (if any). | ||
- Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion about how the `input` URL is handled, given that `input` will not follow the normal URL resolution rules when `prefixUrl` is being used, which changes the meaning of a leading slash. | ||
- After `startPath` and `input` are joined, the result is resolved against the [base URL](https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI) of the page (if any). | ||
- Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion about how the `input` URL is handled, given that `input` will not follow the normal URL resolution rules when `startPath` is being used, which changes the meaning of a leading slash. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leading slashes in
input
are disallowed when using this option to enforce consistency and avoid confusion [...]
From what I can tell from the changed code, this is no longer the case?
I personally prefer it that way though and think that we should keep that behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. As mentioned in the TODOs section, I haven't gotten around to updating the docs yet, other than a simple find & replace. But thank you for the reminder. 🙂
I like #561 because I don't understand why we should throw an error if the input starts with a slash, I read the topic but still don't find this constraint justified. I don't like #606 because I can have a prefix like |
source/core/Ky.ts
Outdated
this._input = this._input.slice(1); | ||
} | ||
|
||
this._input = this._options.startPath + this._input; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if _input
starts with a protocol (or is a valid URL?) then we should ignore startPath
in order to avoid an URL like http://foo.com/http://bar.com
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That kind of "double URL" construct is useful for reverse proxies. It should be supported.
Also, you would be surprised how hard URL parsing is. Especially for a library like Ky that supports non-browsers.
You don't need to split it up like that, you can just put the whole thing in |
Any news on this PR? I'd love to replace Axios with Ky, but this issue is a blocker. |
# Conflicts: # readme.md # source/core/Ky.ts
6ab3b48
to
351020a
Compare
This is almost ready, just need to update the docs and types. Will try to get it landed later this week. |
@sindresorhus this is now ready for review. Currently, it's a breaking change due to the renaming of Most users will simply need to update the option name, but it's recommended to switch to |
This looks like a good improvement to me. I would prefer keeping an alias to not make this change too disruptive. We can add a |
|
||
A prefix to prepend to the `input` URL before making the request. It can be any valid path or URL, either relative or absolute. A trailing slash `/` is optional and will be added automatically, if needed, when it is joined with `input`. Only takes effect when `input` is a string. | ||
|
||
Useful when used with [`ky.extend()`](#kyextenddefaultoptions) to create niche-specific Ky-instances. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is prefix
still preferred for this or should it be moved to baseUrl
?
@@ -191,29 +191,55 @@ Search parameters to include in the request URL. Setting this will override all | |||
|
|||
Accepts any value supported by [`URLSearchParams()`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams). | |||
|
|||
##### prefixUrl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need even clearer docs about the difference between baseUrl
and prefix
and when to use each. Maybe with some real-world example use-cases. Otherwise, it's going to be confusing for users and that ends up being a support burden for us.
@@ -191,29 +191,55 @@ Search parameters to include in the request URL. Setting this will override all | |||
|
|||
Accepts any value supported by [`URLSearchParams()`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams). | |||
|
|||
##### prefixUrl | |||
##### baseUrl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be great to have a matrix like you added in the original pull request description for both the baseUrl and prefix sections that goes through the motions of showing most (if not all) relevant permutations of baseUrl (or prefix), input and the resulting request URL.
This could help de-mystify what both options do and what might be appropriate for any given use case.
I think that's especially useful because not all people will know that "prefix" and "base URL" mean (sometimes subtly) different things (of course these docs make that clear, but still).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was originally going to do that but it felt like a lot of information all at once and made the choices seem complex, which could actually be more confusing than helpful. I went with a simple, explicit recommendation to use baseUrl
unless you know you need prefix
. It seems clearer to me overall.
I think we should see what questions come up and then make tweaks based on that. If there ends up being a lot, we could add a full matrix of examples to the FAQ. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm perfectly fine with that.
My angle is mainly that I think there might be a lot of (potential) users coming from contexts where all they need is setting some API prefix string (e.g. 'https://my-awesome-site.com/api/v1'
) so that their API client code can be simplified to accept path segments without that prefix (e.g. '/users'
or 'users'
) to produce a simply joined request URL (e.g. 'https://my-awesome-site.com/api/v1/users'
). For those cases, developers might never think to care about what difference a leading slash on '/users'
vs no leading slash on 'users'
could make. For them, both are equivalent and should result in the same request URL.
Since it might never occur to them that there can even be a difference between both cases, they might also just gloss over the documentation for prefix and baseUrl assuming (wrongly) that surely it behaves as expected. A tabular representation of the "concatenation" behavior might be a bit of an easier "catch" while reading the docs.
But again, I'm perfectly fine with not having that explained in a tabular manner.
Closes #291
This PR aims to improve the flexibility of input URLs and options for resolving them prior to the request.
prefixUrl
option is renamed toprefix
to avoid implying that it behaves as a URL with resolution semanticsinput
before any other processing takes place, and it can be a path or full URL with host, if needed./api
, even wheninput
contains a leading slash. In most cases, the newbaseUrl
option should be used instead for improved flexibility.baseUrl
option is added which performs URL resolution on the combinedprefix
+input
input
(including anyprefix
) is resolved against thebaseUrl
to determine the final request URL, according to standard URL resolution rules.baseUrl
is similar to using the HTML<base>
tag or changing thewindow.location
before making the request (except it only applies to Ky). As such, the presence of a trailing slash in thebaseUrl
and the presence of a leading slash ininput
(orprefix
, if applicable) can affect the final request URL. For example, ifinput
contains a leading slash, it will bypass the path part of thebaseUrl
, unlike what would happen if the base URL were provided as aprefix
. The host will also be bypassed if theinput
is an absolute URL. This flexibility is what makesbaseUrl
useful (see prefixUrl is unneededly applied to absolute urls #291).baseUrl
can be a path or full URL and it will be resolved againstdocument.baseURI
, if necessary.baseUrl
andprefix
can be used independently or together, they are optional and not mutually exclusive.input
can now be provided with or without a leading slash, regardless of which options are used. Previously, using a leading slash ininput
was not allowed to be combined with a prefix URL. Now, slashes are allowed and automatically normalized so that there will always be a single slash betweenprefix
andinput
, whether or notprefix
ends with a slash orinput
begins with a slash. However,baseUrl
is sensitive to slashes, as explained above. See the table below for examples.Typical usage
baseUrl
prefix
input
'/api/'
''
'users'
/api/users
'http://foo.com/api/'
''
'users'
http://foo.com/api/users
'http://foo.com/api/'
''
'http://bar.com/other'
http://bar.com/other
''
'/api'
'/users'
/api/users
''
'http://foo.com/api'
'/users'
http://foo.com/api/users
Slashes:
prefix
joins,baseUrl
resolvesbaseUrl
prefix
input
''
'http://foo.com/api/v2'
'users'
http://foo.com/api/v2/users
''
'http://foo.com/api/v2'
'/users'
http://foo.com/api/v2/users
''
'http://foo.com/api/v2/'
'users'
http://foo.com/api/v2/users
''
'http://foo.com/api/v2/'
'/users'
http://foo.com/api/v2/users
'http://foo.com/api/v2'
''
'users'
http://foo.com/api/users
'http://foo.com/api/v2'
''
'/users'
http://foo.com/users
'http://foo.com/api/v2/'
''
'users'
http://foo.com/api/v2/users
'http://foo.com/api/v2/'
''
'/users'
http://foo.com/users