Determining object keys based on the values of other keys to customize functions

In search of a way to create a function with statically typed input object keys based on certain key values, I have created a TypeScript playground containing a failing test. Click the link below to access the playground:

Playground Link

export type Input<$Method extends URL | 'memory'> = {
  method: $Method
} & ($Method extends URL ? { headers?: HeadersInit } : {})

declare const foo: <$Input extends Input<URL | 'memory'>>(input:$Input) => $Input

foo({ method: new URL('http://abc'), headers: {} }) // OK
foo({ method: new URL('http://abc') }) // OK
foo({ method: 'memory' }) // OK

// @ts-expect-error headers should be disallowed
foo({ method: 'memory', headers:{} })
//                      ^^^^^^^^^^ -- should error

The challenge remains unresolved where headers is expected only when method equals URL. I am inquiring about alternative methods to achieve this.

An additional observation shows that the inferred type is accessible at the output position (return).

-- edit

I discovered one solution utilizing an Exact type helper, but I am curious if there's a simpler approach available. Access the provided link here.

Answer №1

Your generic Input<$Method> type does not behave as expected when $Method is the union type URL | 'memory'. Essentially, it boils down to

{ method: "memory" | URL; } | { method: "memory" | URL; headers?: HeadersInit; }
, resulting in just
{ method: "memory" | URL; headers?: HeadersInit; }
, allowing headers even with "memory" as method.

Considering that you only use Input<URL | 'memory'> in foo(), there is no need for Input to be generic. Once you establish what Input<URL | 'memory'> should represent, you can directly replace Input with that specific definition. A good option would be to create a discriminated union like

type Input = { method: "memory" } | { method: URL, headers?: HeadersInit }

If foo were not a generic function, this setup would work effectively, assuming object literals are provided for excess property checks to take effect:

declare const foo: (input: Input) => Input
foo({ method: new URL('http://abc'), headers: {} }) // OK
foo({ method: new URL('http://abc') }) // OK
foo({ method: 'memory' }) // OK
foo({ method: 'memory', headers: {} }) // error 👍

However, due to foo's generic nature and $Input's constraint to Input, additional properties are permitted:

declare const foo: <$Input extends Input>(input: $Input) => $Input
foo({ method: new URL('http://abc'), headers: {} }) // OK
foo({ method: new URL('http://abc') }) // OK
foo({ method: 'memory' }) // OK
foo({ method: 'memory', headers: {} }) // Extra allowed 👎

In order to explicitly forbid headers when method is set to "memory", include an impossibility declaration in that union member as follows:

type Input = { method: "memory", headers?: never } | { method: URL, headers?: HeadersInit }

An optional property of the never type implies that the property can either be missing or undefined, since no values exist of that type.

foo({ method: new URL('http://abc'), headers: {} }) // OK
foo({ method: new URL('http://abc') }) // OK
foo({ method: 'memory' }) // OK    
foo({ method: 'memory', headers: {} }) // Unwanted 👍

This revised Input type structure is recommended for your scenario.


Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue encountered: In TypeScript, an error has been identified in the file three-core.d.ts located in the node_modules directory. Specifically, at line 767 and character 24, the error code TS2304

Encountering an issue: TypeScript error: node_modules/@types/three/three-core.d.ts(767,24): Error TS2304: Cannot find name 'Iterable'. Take a look at the screenshot for reference Following the gulp workflow instructions from this guide: ht ...

What is the syntax for creating multi-line function definitions in TypeScript?

When it comes to writing functions with numerous parameters, do you typically format them like this: function foo( a: number, b: string, c: boolean): boolean { .... } or do you prefer formatting them like this: function foo( a: number, ...

Issue with using useState inside alert: unexpected empty array

I am facing an issue with the 'exercises' useState in my code. The useEffect function correctly prints the 'exercises' after every change, but when I press the 'Finish' button, the 'exercises' suddenly become empty. ...

It is advisable for the subscriber not to halt upon encountering an error within the

Dealing with a problematic subscriber that automatically unsubscribes itself when an error occurs: observable .subscribe( (data) => { // logic encountering an error // similar to throw new Error() } ) To avoid this, I can use t ...

Ignoring the no-unused-vars rule from StandardJS even though variables are being used

Within my Typescript React project, I have declared the following: export type NavState = { mounted: boolean } I then proceeded to use this within a component like so: import { NavState } from '../../models/nav' class Nav extends React.Compon ...

Building an admin dashboard sidebar layout in Next JS 13: A step-by-step guide

Currently, I am in the process of designing a layout for my dashboard. The dashboard features a sidebar with links to various pages, but upon navigating to the dashboard page, only my index.tsx is visible without the accompanying layout and sidebar. I hav ...

When using Angular 4 CanActivate guard in conjunction with a service, the component may not load properly. However, by simply using Observable

When attempting to implement the CanActivate guard in Angular, I encountered an issue where it didn't work when calling a service and using return true, or even return Observable.of(true);. However, the guard did work and the component loaded success ...

AngularJS attempted to open $modal, but it encountered an error because the controller named <controllername> was not registered

Currently, I am in the process of enhancing a web application that was originally developed using AngularJS with typescript instead of JS. I have a controller set up with a specific template that I want to display in a modal using $modal.open. However, I ...

Trigger the Angular Dragula DropModel Event exclusively from left to right direction

In my application, I have set up two columns using dragula where I can easily drag and drop elements. <div class="taskboard-cards" [dragula]='"task-group"' [(dragulaModel)]="format"> <div class="tas ...

Set up SystemJS to properly load my Angular 2 component module

Recently, I made the switch from ng1 to ng2. I successfully imported Angular 2 and its modules into my project: <script src="/node_modules/systemjs/dist/system.src.js"></script> <script src="/node_modules/rxjs/bundles/Rx.js"></script& ...

What improvements can I implement in this React Component to enhance its efficiency?

Seeking advice on improving the efficiency of this React Component. I suspect there is code repetition in the onIncrement function that could be refactored for better optimization. Note that the maxValue prop is optional. ButtonStepper.tsx: // Definition ...

Guide to implementing scheduled tasks in a Node.js API using Express

Currently, my Node API has multiple endpoints, and while they work well for the most part, there is one endpoint that struggles with processing large requests taking up to 1 hour. To handle this, I am considering implementing a system where instead of wait ...

I'm searching for TypeScript interfaces that can be used to define OpenAPI Json. Where can I

If you're looking to implement the OpenApi specifications for your project, there are a variety of fields and values that need to be set. For a detailed look at these specifications, you can refer to the documentation here. In an effort to streamline ...

Encountering a TypeScript error when trying to establish a connection to MariaDB using node

working with mariadb npmjs version: 2.1.2 import mariadb from "mariadb"; const pool = mariadb.createPool({host: process.env.DBHOST, user: process.env.DBUSER, password: process.env.DBPASS, port: process.env.DBPORT, connectionLimit: process.env.DBCONNLIMIT, ...

How can CSS variables be applied to a hover property within an Angular directive?

Check out the directive below: import { Directive, ElementRef, HostListener } from '@angular/core'; @Directive({ selector: 'd-btn', host: {} }) export class ButtonDirective { constructor(private el: ElementRef){} @HostLis ...

Ensuring that the app closes completely before launching a new instance with webpack hot module reload

My nest.js application is utilizing webpack hot module reload (hmr), but I am encountering an issue where the reload does not allow the old instance to fully close (including the database connection and telegram bot) before launching a new instance. This c ...

What event type should be used for handling checkbox input events in Angular?

What is the appropriate type for the event parameter? I've tried using InputEvent and HTMLInputElement, but neither seems to be working. changed(event) { //<---- event?? console.log({ checked: event.target.checked }); } Here's the com ...

A guide to incorporating Material-UI ThemeProvider and WithStyles with Typescript

I've been feeling frustrated lately as I've been dedicating the past few days to migrating my React application from JavaScript to TSX. While I appreciate the type checking that TSX provides, I'm struggling with understanding how to implemen ...

Leveraging TypeScript's library extensions in combination with requirejs

Currently, I am experimenting with THREE.js and socket.io to develop a small game. I am looking to utilize the OrbitControls extension for THREE.js for camera controls, which I have successfully used in a previous project. However, it seems that the clien ...

Having some trouble while attempting to set up the next-auth@beta package due to an error

Currently encountering the following error message: code EUNSUPPORTEDPROTOCOL Unsupported URL Type "workspace:": workspace:* I have made sure to update my node to the most recent recommended version. In a previous project, I successfully instal ...