-
Notifications
You must be signed in to change notification settings - Fork 616
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
Remove /// <reference lib="dom" />
from fetch-types.ts
#401
Conversation
|
hey @huw, thanks for putting together such a thorough analysis. the problem makes total sense. options (3) or (4) seem best to me, in that they shouldn't break any existing consumers. if you'd be willing to implement one of those then I'm happy to get this merged for you! let me know if that looks like a larger lift, and we can discuss alternatives |
I went back and forth a bit on the best implementation, but since this library hits a relatively small surface area on One other exception: I then decided to keep the existing assertion tests we had in place, which meant adding I also added assertion tests for |
package.json
Outdated
@@ -44,6 +44,7 @@ | |||
"node-fetch": "^2.6.1" | |||
}, | |||
"devDependencies": { | |||
"@cloudflare/workers-types": "^4.20230628.0", |
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.
What do you think about just redefining the types inline, similar to https://github.com/michaelcpuckett/activity-kit/blob/2119ea8e95fde451d5057d7bd6d95330de34c15e/packages/types/lib/adapters/FetchPolyfill.d.ts? I think it's a little confusing to leak refs from the cloudflare package in source (even though it's only a devDependecy)
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.
Approach generally looks OK to me - but I have one question about the workers-types dependency
tsconfig.json
Outdated
@@ -8,8 +8,8 @@ | |||
"module": "commonjs", | |||
// "esModuleInterop": true, | |||
|
|||
// Overrides default in order to remove "dom" because this package shouldn't assume the presence of browser APIs | |||
"lib": ["ES2019"], | |||
// Include `DOM` for access to the `fetch` global type definition. It won't be included in the built declaration files, but they'll assume the presence of that definition or an equivalent (ex. `@types/node`, `@cloudflare/workers-types`; see #401). |
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.
Could we just declare this ourselves (declare let fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
) for the assert on fetch-types.ts:74, or is this still necessary?
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.
RE both questions; the reason to do it this way is to keep the assertions up to date if the underlying libraries change. If we wanted to avoid this I’d just remove the relevant assertions entirely; there’s not much point in asserting that 1 === 1
.
If I were writing this library I’d drop the other 2 assertions (keeping the node-fetch
ones because they don’t have downsides) and just be mindful if the fetch spec ever updates (rare and unlikely to affect the subset we use), but given that we already had assertions in the codebase I wasn’t sure if the repo’s stance was to keep those tests in place. LMK your take and I’ll update the PR :)
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 agree with the way you'd write it and low likelihood that the a fetch spec change would break this (or that @types/node will give us a better way to represent this by the time it does).
One question though - if we go this route, we can't rely on the node-fetch
types to be compatible with DOM fetch
right? AFAICT it's easy to update headers
via NonNullable<Record<string, string>>
but body
is harder since ReadableStream | XMLHttpRequestBodyInit
has no equivalent node types. I might be missing something there though
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’ve removed the two extra assertions and added some clarifying comments ^_^
You are correct—node-fetch
, DOM fetch
, native Node fetch (via undici
), Cloudflare fetch, etc. are all incompatible on the margins. Here’s an insightful comment that explains some of it. We just have to hope that future contributors only the common subset of fetch features across implementations (which, as above, is quite large and unlikely to regularly change), or, as we do with Agent
, make it optional.
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.
Got it, I see how you solved body
(which was my primary concern) and left one comment on that part. In agreement on the rest of it!
method?: RequestInit["method"] | ||
redirect?: RequestInit["redirect"] | ||
agent?: Agent | ||
body?: string |
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 there any harm to also including the node-fetch
body types (i.e. NodeRequestInit["body"]
) here too? I'm concerned that there might be folks using Blob
or stream types via node here that this breaks
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.
The short answer is no.
The long answer is (my apologies if I’m explaining stuff you already know): TypeScript uses duck typing, which means that as long as the user-provided fetch
implementation accepts body: string
, it doesn’t matter what else their fetch implementation accepts. We’re just specifying the minimal contract of things we need to use, which in our case is just string | undefined
since in Client.ts
, the only place fetch
is used, we pass in the result of JSON.stringify()
or undefined
. For example, tRPC accept custom fetch
implementations but only type a subset of body
, which corresponds with what they actually use internally.
However, in practice, extending our contract wouldn’t hurt. Right now we type body: NonNullable<RequestInit["body"]> & NonNullable<NodeRequestInit["body"]>
, which uses an intersection over the DOM and node-fetch
types. This computes to something like body: Blob | URLSearchParams | FormData | string
(I’m hazy on this, but the point is that, as an intersection, it removes anything that’s not the same type between implementations, such as NodeJS.ReadableStream
). In this PR, taking the intersection over string & NodeRequestInit["body"]
would compute to string
, which is kind of pointless (but doing string | NodeRequestInit["body"]
would cause all manner of incompatibilities because we’d require every client to accept NodeJS.ReadableStream
, to give an example).
So I think it wouldn’t make sense to add this.
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.
Thanks for the explanation! I had missed that we don't allow the user to pass through a body
, and always pass through the result of JSON.stringify()
-- so makes sense why keeping string
is perfectly fine.
FWIW in the process of answering some of those tests I cooked up a few MWEs on my machine and made sure that the types are still compatible with I am about as confident as I can be that this won’t cause problems with the types & as such I think this should be a bugfix release (especially because it doesn’t touch the actual implementation). |
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.
Looks great, thanks again for making this change and helping me understand the implications to ensure it's safe! I'll include this in a patch release shortly.
Problem
I am running the package inside Cloudflare Workers, and I’m using
types: ["@cloudflare/workers-types"]
to import Cloudflare’s type definitions. This imports a Cloudflare-specificRequest
type, which is mostly compatible with thelib.dom.d.ts
Request
, except in edge cases. The problem is that this package uses a triple-slash reference to re-importlib.dom.d.ts
:notion-sdk-js/src/fetch-types.ts
Line 1 in ab29303
The effect is that any package which directly or indirectly relies on
@notionhq/client
usesRequest
fromlib.dom.d.ts
, and not the one from@cloudflare/workers-types
, which is the one I want. This causes a handful of frustrating type errors due to the incompatibilities between the two.Why is it this way right now?
The way the
fetch-types.ts
file is written suggests that the package is trying to be lenient about whichfetch
function the user brings with them, so it takes a union over the browser & Nodefetch
es. However, we can’t writeimport { fetch } from "lib.dom.d.ts"
to get just that type. We have a handful of options:DOM
totsconfig.json
’stypes
/// <reference lib="dom" />
tofetch-types.d.ts
What’s the trade-off? (Updated 2023-07-10)
We’ve decided against (1) in the past in order to maintain the isomorphic nature of the library, in the sense that it can be imported from any TypeScript context and is self-containing. If we included
DOM
, users would be required to bring their own versions of the types that are implicitly imported (in this case,fetch
) in their owntsconfig.json
s. This would most commonly be done by includingDOM
themselves, but users in other environments could include@cloudflare/workers-types
or@types/node
, for example (which they would be doing anyway in order to run in whichever environment they’re compiling to). However,@types/node
doesn’t include a global fetch definition and may not for some time (DefinitelyTyped/DefinitelyTyped#60924).tRPC solves this issue by importing
RequestInfo
/RequestInit
, which are present in@types/node
and reconstructing the fetch function.(2), which the library does today, pollutes the global namespace as discussed above.
I’m writing this PR because I don’t think (2) makes as much sense as it used to and I want to start that conversation; as other runtimes proliferate and pre-18 versions of Node become more scarce, the trade-off in user support is swinging more toward (1). Arguably, long-term we should do (3) or (4) and I’d be happy to PR those also if the maintainers prefer :)