TypeScript-DOM-lib-generator icon indicating copy to clipboard operation
TypeScript-DOM-lib-generator copied to clipboard

[Web API type definition issue] `exactOptionalPropertyTypes` makes working with most of the interfaces a nightmare

Open rijenkii opened this issue 4 months ago • 5 comments

Summary

Optional fields in lib.dom.d.ts do not explicity have undefined in their types, which makes their construction needlessly difficult

Expected vs. Actual Behavior

Expected following code to just work:

function fetchUsers(params?: { signal?: AbortSignal | undefined }) {
  return fetch("/api/v1/user/", { signal: params?.signal });
}

But instead the following error is shown:

Argument of type '{ signal: AbortSignal | undefined; }' is not assignable to parameter of type 'RequestInit' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
  Types of property 'signal' are incompatible.
    Type 'AbortSignal | undefined' is not assignable to type 'AbortSignal | null'.
      Type 'undefined' is not assignable to type 'AbortSignal | null'. (2379)

Following is required to be done instead (let's pretend that RequestInit.signal cannot be set to null):

function fetchUsers(params?: { signal?: AbortSignal | undefined }) {
  const init: RequestInit = {};
  if (params?.signal) {
    init.signal = params.signal;
  }

  return fetch("/api/v1/user/", init);
}

Playground Link

https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true#code/GYVwdgxgLglg9mABMAplCALAqgZxQJxwAoAHAQ3zIFscB+ALkQG9EcYBzMMgGwcQEEARnHxQAyhy7dEAH0TgAJimAwwKBYgC+ASmYAoRInxoQ+JKnQYiAIgD0ZEjFsA3AIy2QefLesAaZqySPIzklDS0AHRsnDxa2gDceppAA

Browser Support

  • [x] This API is supported in at least two major browser engines (not two Chromium-based browsers).

Have Tried The Latest Releases

  • [x] This issue applies to the latest release of TypeScript.
  • [x] This issue applies to the latest release of @types/web.

Additional Context

See https://webidl.spec.whatwg.org/#idl-dictionaries:

In the JavaScript binding, a value of undefined for the property corresponding to a dictionary member is treated the same as omitting that property.

I have not found a single | undefined on an optional interface field in the entire lib.dom.d.ts of Typescript 5.9.2.

rijenkii avatar Aug 18 '25 04:08 rijenkii

Hello, Can I work on this @jakebailey? It is an easy fix

Bashamega avatar Aug 18 '25 05:08 Bashamega

It's easy if it's added everywhere, but I do know if that's quite the right fix

jakebailey avatar Aug 18 '25 05:08 jakebailey

It's easy if it's added everywhere, but I do know if that's quite the right fix

I was thinking of adding undefined here: https://github.com/microsoft/TypeScript-DOM-lib-generator/blob/fad434be5961529bee396efab03cce2a1bc333e2/src/build/emitter.ts#L497C1-L501C4 Alternatively, I can make a new kdl file which stores the properties that need to be undefined, but this would require more code, and may result in a long file.

Bashamega avatar Aug 18 '25 05:08 Bashamega

If optional fields are supposed to be nullable, then something very wrong is going on. This is how RequestInit looks currently:

interface RequestInit {
    /** A BodyInit object or null to set request's body. */
    body?: BodyInit | null;
    /** A string indicating how the request will interact with the browser's cache to set request's cache. */
    cache?: RequestCache;
    /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */
    credentials?: RequestCredentials;
    /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */
    headers?: HeadersInit;
    /** A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */
    integrity?: string;
    /** A boolean to set request's keepalive. */
    keepalive?: boolean;
    /** A string to set request's method. */
    method?: string;
    /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */
    mode?: RequestMode;
    priority?: RequestPriority;
    /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */
    redirect?: RequestRedirect;
    /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */
    referrer?: string;
    /** A referrer policy to set request's referrerPolicy. */
    referrerPolicy?: ReferrerPolicy;
    /** An AbortSignal to set request's signal. */
    signal?: AbortSignal | null;
    /** Can only be null. Used to disassociate request from any Window. */
    window?: null;
}

Not all optional fields have | null, so I do not think that changing makeNullable will fix this.

rijenkii avatar Aug 18 '25 05:08 rijenkii

I think this is the case, because the web IDL compiler doesn't add null when it is optional. So this would require a separate update.

Bashamega avatar Aug 18 '25 05:08 Bashamega