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

TypeScript cannot figure out which Got type to use #954

Closed
1 task done
scttcper opened this issue Dec 1, 2019 · 30 comments · Fixed by #1051
Closed
1 task done

TypeScript cannot figure out which Got type to use #954

scttcper opened this issue Dec 1, 2019 · 30 comments · Fixed by #1051
Labels
external The issue related to an external project ✭ help wanted ✭ types The issue is related to TypeScript

Comments

@scttcper
Copy link
Contributor

scttcper commented Dec 1, 2019

What would you like to discuss?

With @types/got we could use type GotOptions and be able to pass this to got.get(options) however with v10 the types are string | OptionsOfDefaultResponseBody and OptionsOfDefaultResponseBody and its friends are not available to import at the root of the project. I was wondering if this was on purpose.

What i'm looking for and not sure how to do with v10

const options: GotOptions = {
	url: 'https://example.com',
};
const response = await got(options);

Also, this example from the docs isn't working for me and I'm wondering if it should accept a partial? Opened #953 with a demo

got.mergeOptions(got.defaults.options, {
	responseType: 'json',
});

Checklist

  • I have read the documentation.

Really excited about the release of v10 🎉

@szmarczak
Copy link
Collaborator

What i'm looking for and not sure how to do with v10

I don't understand the example. The URL is invalid.

@yovanoc
Copy link

yovanoc commented Dec 2, 2019

We have this error when we try to do that

image

The only way I had to manage this is to do an as const and don't declare as GotOptions like this:

const options = {
    method,
    cookieJar,
    agent,
    responseType: "json",
    form,
    headers
  } as const;

await got(url, options);

@scttcper
Copy link
Contributor Author

scttcper commented Dec 2, 2019

I don't understand the example. The URL is invalid.

Sorry was being too terse. There would be a bunch of properties and a valid url passed to options, there just isn't a good type to import and use.

@szmarczak szmarczak added types The issue is related to TypeScript ✭ help wanted ✭ labels Dec 2, 2019
@szmarczak
Copy link
Collaborator

const options: GotOptions = {
	url: 'https://example.com'
};

This is correct. The example:

const response = await got(options);

fails because it tries to use

<T>(url: string | Merge<Options, {isStream: true}>, options?: Merge<Options, {isStream: true}>): ProxyStream<T>;

instead of

<T = string>(url: string | OptionsOfDefaultResponseBody, options?: OptionsOfDefaultResponseBody): CancelableRequest<Response<T>>

The Got interface looks like:

export type OptionsOfDefaultResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType?: 'default'}>;
type OptionsOfTextResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'text'}>;
type OptionsOfJSONResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'json'}>;
type OptionsOfBufferResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'buffer'}>;
type ResponseBodyOnly = {resolveBodyOnly: true};

interface GotFunctions {
	// `asPromise` usage
	<T = string>(url: string | OptionsOfDefaultResponseBody, options?: OptionsOfDefaultResponseBody): CancelableRequest<Response<T>>;
	(url: string | OptionsOfTextResponseBody, options?: OptionsOfTextResponseBody): CancelableRequest<Response<string>>;
	<T>(url: string | OptionsOfJSONResponseBody, options?: OptionsOfJSONResponseBody): CancelableRequest<Response<T>>;
	(url: string | OptionsOfBufferResponseBody, options?: OptionsOfBufferResponseBody): CancelableRequest<Response<Buffer>>;

	// `resolveBodyOnly` usage
	<T = string>(url: string | Merge<OptionsOfDefaultResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfDefaultResponseBody, ResponseBodyOnly>): CancelableRequest<T>;
	(url: string | Merge<OptionsOfTextResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfTextResponseBody, ResponseBodyOnly>): CancelableRequest<string>;
	<T>(url: string | Merge<OptionsOfJSONResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfJSONResponseBody, ResponseBodyOnly>): CancelableRequest<T>;
	(url: string | Merge<OptionsOfBufferResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfBufferResponseBody, ResponseBodyOnly>): CancelableRequest<Buffer>;

	// `asStream` usage
	<T>(url: string | Merge<Options, {isStream: true}>, options?: Merge<Options, {isStream: true}>): ProxyStream<T>;
}

And if we do:

type CorrectGotType = <T = string>(url: string | OptionsOfDefaultResponseBody, options?: OptionsOfDefaultResponseBody) => CancelableRequest<Response<T>>;

const typedInstance = instance as CorrectGotType;
await typedInstance(options as GotOptions);

it still fails because:

Argument of type 'GotOptions' is not assignable to parameter of type 'Merge<Merge<RequestOptions, Merge<GotOptions, URLOptions>>, { isStream?: false | undefined; resolveBodyOnly?: false | undefined; responseType?: "default" | undefined; }>'.
  Type 'GotOptions' is not assignable to type '{ isStream?: false | undefined; resolveBodyOnly?: false | undefined; responseType?: "default" | undefined; }'.
    Types of property 'isStream' are incompatible.
      Type 'boolean | undefined' is not assignable to type 'false | undefined'.
        Type 'true' is not assignable to type 'false | undefined'.ts(2345)

TS is dumb, because it ignores the fact that we handle isStream seperately for true and false.

@szmarczak
Copy link
Collaborator

microsoft/TypeScript#24929 (comment)

@szmarczak

This comment has been minimized.

@szmarczak
Copy link
Collaborator

szmarczak commented Dec 2, 2019

If we extend GotFunctions by

	// Generic usage
	<T>(url: string | Options, options?: Options): CancelableRequest<T> | ProxyStream<T>;

then it would pass but you would have to cast it as CancelableRequest<Response<string>> by yourself which is not convenient.

The fix would be to use T extends OptionsOfResultA ? A : T extends OptionsOfResult B ? B : ... as it works as expected. It doesn't work as expected.

@szmarczak szmarczak added the external The issue related to an external project label Dec 2, 2019
@szmarczak szmarczak changed the title migrating from GotOptions TypeScript cannot figure out which Got type to use Dec 2, 2019
@sindresorhus
Copy link
Owner

// @pmmmwh

@viceice
Copy link

viceice commented Dec 3, 2019

Workaround:

type OptionsOfJSONResponseBody = Merge<
    Options,
    {
        isStream?: false;
        resolveBodyOnly?: false;
        responseType: 'json';
    }
>;
await got<ResultType>(url, opts as OptionsOfJSONResponseBody)

@pmmmwh
Copy link
Contributor

pmmmwh commented Dec 3, 2019

// @pmmmwh

I am thinking, maybe we should expose some of the internal option types to the outside world? Or instead, exporting a type for promise and another one for stream?

That seems like a good idea to make sure TypeScript infers them correctly. For example, the following works:

const options: OptionsOfDefaultResponseBody = {
	url: 'https://example.com',
};
const response = await got(options);

Edit:
Actually, thinking about it - part of the issue is that we strictly type options' responseType/isStream/resolveBodyOnly properties, while in the actual world if the user does not do as const an object (even with responseType: 'text' or isStream: false) will always be considered as responseType?: string/isStream?: boolean. This make all our specific overloads fail and thus fallback to the last one, which is the one for streams. I will have to test if we can workaround that while typing with high specificity.

@mrhyde
Copy link
Contributor

mrhyde commented Jan 9, 2020

@pmmmwh is right about the bigger picture of this issue. Just trying to proxy arguments types to nested function

  const queuedGet = (...args: Parameters<GotFunctions>) =>
    queue.add(() => got.get(...args))

will fail with error

Types of property 'isStream' are incompatible.
  Type 'false | undefined' is not assignable to type 'true'.

P.S. I have submitted a PR for exporting GotFunctions interface #1017

@novemberborn
Copy link
Contributor

TS is dumb, because it ignores the fact that we handle isStream seperately for true and false.

Put differently, the type definitions in Got are so complex they're bumping up against TypeScript's limits.

There's little point railing against TypeScript here. Got's ideal API may just not be expressible. You should consider moving a little closer to what TypeScript can handle.

With my compile errors so far, it seems like providing the URL as a url property fixes things.

@szmarczak
Copy link
Collaborator

Put differently, the type definitions in Got are so complex they're bumping up against TypeScript's limits.

See function overloads. The same should be possible when using interfaces IMO.

@lagden
Copy link

lagden commented Jan 20, 2020

go back to the old and good JavaScript...
you are making up problems. IMO.

@bkhartmire
Copy link

bkhartmire commented Jan 21, 2020

I was coming across this error as well when trying to use got.post and pass in options.

Argument of type 'GotOptions' is not assignable to parameter of type 'Merge<Merge<RequestOptions, Merge<GotOptions, URLOptions>>, { isStream?: false | undefined; resolveBodyOnly?: false | undefined; responseType?: "default" | undefined; }>'.
  Type 'GotOptions' is not assignable to type '{ isStream?: false | undefined; resolveBodyOnly?: false | undefined; responseType?: "default" | undefined; }'.
    Types of property 'isStream' are incompatible.
      Type 'boolean | undefined' is not assignable to type 'false | undefined'.
        Type 'true' is not assignable to type 'false | undefined'.ts(2345)

It worked when I used got.extend instead.

const body = {example: 'hello'}

const api = got.extend({
    prefixUrl: 'example.com',
    responseType: 'json',
});

const res = await api('/endpoint', {method: 'POST', json: body})

@cah-kyle-dunn
Copy link

cah-kyle-dunn commented Jan 28, 2020

The entire problem is a result of the dynamic return type being inferred from values in the options object. IMO the simplest workaround is to avoid using the isStream, responseType and resolveBodyOnly options altogether.

// got.get<CustomType>(url, { responseType: 'json' })
got.get(url).json<CustomType>()

// got.get(url, { responseType: 'blob' })
got.get(url).blob()

// got.get(url, { responseType: 'text' })
got.get(url).text()

// got.get(url, { isStream: true })
got.stream(url)

This allows for a base options type that always matches the default overload (i.e. OptionsOfDefaultResponseBody).

type StrictOptions = Omit<Options, 'isStream' | 'responseType' | 'resolveBodyOnly'>

I might even suggest StrictOptions be an exported type from got.

@resynth1943
Copy link

resynth1943 commented Feb 14, 2020

I'm bumping up against something with the typings, although it might not be related to this issue. After an update, it no longer seems possible to import FormOptions and friends from got; This breaks my code.

I also can't import GotPromise and a load of other stuff. What is happening? Do we know of any solutions to this yet?

@resynth1943
Copy link

Well if we don't, I'll explore other alternatives. This is really weird, as it was working great last time I checked.

Thanks anyway for the package, Sindre. ❤️

@mrhyde
Copy link
Contributor

mrhyde commented Feb 24, 2020

@resynth1943 I'm not sure where you managed to find FormOptions but there was no such export unless you are confusing it with GotOptions

@resynth1943
Copy link

Nope. I'm pretty sure it exported something named FormOptions. Possibly GotFormOptions. Nevertheless, my code was using it, so it most likely exists, unless the setup was invalid.

@mrhyde
Copy link
Contributor

mrhyde commented Feb 24, 2020

You were using @types/got which was made for an older version. You need to update your code to v10 or rollback. It has nothing to do with this issue.

@resynth1943
Copy link

You were using @types/got which was made for an older version. You need to update your code to v10 or rollback. It has nothing to do with this issue.

Well that package has a lot of weekly downloads. 171,998, to be exact. It's an understandable point of confusion. 😛

@viceice
Copy link

viceice commented Feb 24, 2020

Maybe someone should add a package removal pr to for @types/got

@cesarfd
Copy link

cesarfd commented Feb 24, 2020 via email

@viceice
Copy link

viceice commented Feb 24, 2020

That what the link above does, it will remove the types from the repo and pushes a new version to npm with deprecation notice

@szmarczak
Copy link
Collaborator

Maybe someone should add a package removal pr to for @types/got

Some people are still using Got 9 so removal is inappropriate

@viceice
Copy link

viceice commented Feb 24, 2020

the type are still available on npm, they will only be removed from git repo. on npm only a new deprecated version (here major) will be published.

szmarczak added a commit to szmarczak/got that referenced this issue Feb 29, 2020
szmarczak added a commit to szmarczak/got that referenced this issue Mar 3, 2020
szmarczak added a commit to szmarczak/got that referenced this issue Mar 4, 2020
@rifler
Copy link
Contributor

rifler commented Mar 5, 2020

My problem is that some of my dependencies use got@9 and @types/got. So i cant update my project to 10 before i update that dependencies.

maybe we should create two new packages got-legacy and @types/got-legacy with all history from got@9 and behind.
After that changing dependencies will be easy - just replace require('got') to require('got-legacy') without additional testing.
And after that you can update your project to use got@10

@scttcper
Copy link
Contributor Author

scttcper commented Mar 5, 2020

This issue is about v10 stream vs cancellable request response types not 10’s built in types vs 9’s definitely typed package.

@mattddean
Copy link

Another workaround for those still using Got v10

const textRequest = got.extend({ responseType: 'text' })
const res = await textRequest.put('https://api.example.com/v1/whatever', {
  json: { some: 'property' },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
external The issue related to an external project ✭ help wanted ✭ types The issue is related to TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.