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

RequestInit.signal type incompatibility blocks compiling on NodeJS #508

Open
devhawk opened this issue Nov 2, 2023 · 1 comment
Open

Comments

@devhawk
Copy link

devhawk commented Nov 2, 2023

There appears to be an issue in how RequestInit type is declared for DOM and Node 20.x that's blocking me from using oazapfts in server-side Node code.

in @types/node (v20.8.10), RequestInit type definition is imported from the undici-types package. In v5.26.5 of that package, RequestInit.signal is defined like this:

  signal?: AbortSignal // aka AbortSignal | undefined

In lib.dom.d.ts (TS 5.2.2), RequestInit.signal is defined like this:

    signal?: AbortSignal | null; // aka AbortSignal | null | undefined

Which is not compatible with the Node @types version.

It appears that oazapfts is compiled against the DOM declaration of RequestInit. The return type of json, form and multipart functions is not specified in code. The generated declaration file has the DOM version of RequestInit that supports null as an option for signal. Meanwhile, fetchText, fetchJson and fetchBlob all take a FetchRequestOpts as their 2nd argument.

From lib/runtime/index.d.ts in the oazapfts package

    fetchJson: <T extends ApiResponse>(url: string, req?: FetchRequestOpts) => Promise<WithHeaders<T>>;

    json({ body, headers, ...req }: JsonRequestOpts): {
        headers: {
            "Content-Type": string;
        };
        body?: string | undefined;
        baseUrl?: string | undefined;
        fetch?: typeof fetch | undefined;
        formDataConstructor?: (new () => FormData) | undefined;
        cache?: RequestCache | undefined;
        credentials?: RequestCredentials | undefined;
        integrity?: string | undefined;
        keepalive?: boolean | undefined;
        method?: string | undefined;
        mode?: RequestMode | undefined;
        redirect?: RequestRedirect | undefined;
        referrer?: string | undefined;
        referrerPolicy?: ReferrerPolicy | undefined;
        signal?: AbortSignal | null | undefined; // not compatible with Node's declaration of RequestInit
        window?: null | undefined;
    };

Arguably, the right solution here would be to update undici-types to match the declaration in lib.dom.d.ts and wait for that update to eventually get pulled into @types/node. However, oazapfts can work around this in one of two ways:

  1. Explicitly specifying the return type of json, form and multipart as FetchRequestOpts.
  2. Modify the implementation of those functions so that signal is always typed as AbortSignal | undefined, which is compatible with both DOM and Node's declaration of RequestInit.
    json({ body, headers, ...req }: JsonRequestOpts) {
      return {
        ...req,
        ...(body != null && { body: JSON.stringify(body) }),
        // the following line to forces signal's type to AbortSignal | undefined
        signal: req.signal ? req.signal : undefined,
        headers: {
          ...headers,
          "Content-Type": ensureJsonContentType(
            String(headers?.["Content-Type"]),
          ),
        },
      };
    },

I'm happy to open a PR to fix this. Please let me know which approach is preferable.

devhawk added a commit to dbos-inc/dbos-demo-apps that referenced this issue Nov 6, 2023
Updates shop frontend to use
[oazapfts](https://github.com/oazapfts/oazapfts) and payment frontend to
use [OpenAPI Typescript
Codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) for
generating client code.

I used two different code generators because I discovered a [bug in
oazapfts](oazapfts/oazapfts#508) that prevents
it's use in payment after I had already updated shop.

Note, I'm using utility types to give nice type names to the generated
types that have ugly names or are anonymous types. For example, in
payment client, I use the following to give a friendly name to the type
retuned by `getSessionInformation`

```ts
type PaymentSessionInformation = Awaited<ReturnType<typeof api.getSessionInformation>>
```

shop is even more interesting because oazapfts returns a response object
w/ the return value in the `data` field. So I created a custom utility
type to extract the data field's type and combined it with `Awaited` and
`ReturnType` to create friendly type names for use in the rest of the
code:

```ts
type DataFieldType<T> = T extends { data: infer U } ? U : never
type OazapftsReturn<T extends (...args: any) => any> = DataFieldType<Awaited<ReturnType<T>>>
export type Product = OazapftsReturn<typeof $api.getProduct>
```
@Xiphe
Copy link
Collaborator

Xiphe commented Nov 27, 2023

Thanks for bringing this up. Unless its a really easy fix I'd prefer when this can be sorted at the root-cause

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants