Is it possible for a function type to accept intersected types as parameters in TypeScript?

Let's say I have these type definitions:

export type Event = {
  name: EventName,
  timestamp: Timestamp,
};

export type EventWithMetadata = Event & {
  deviceId: ID,
};

I want to create a common function interface like this:

export type SideEffect = (event: Event, run?: Run) => Partial<Run>;

and utilize it with the following function

// Please note that there are many similar side-effect functions available, each working with different variations of the Event type. This is just one example.
const updateTiming: SideEffect = (event: EventWithMetadata, run) => {
  log.debug('updateTiming: %j', event);
  return {
    lastUpdated: event.timestamp,
    deviceId: event.deviceId,
  };
};

However, I encounter this error:

error TS2322: Type '(event: EventWithMetadata, run: Run | undefined) => { startTime: number; deviceId: string; }' is not assignable to type 'SideEffect'.
  Types of parameters 'event' and 'event' are incompatible.
    Type 'Event' is not assignable to type 'EventWithMetadata'.
      Property 'deviceId' is missing in type 'Event' but required in type '{ deviceId: string; }'.

This error makes sense as TypeScript assumes I am trying to convert an Event into an EventWithMetadata, which is not possible.

After exploring various options such as generics, overloads, and union types, the most suitable solution I found is described in this post. However, I am not fond of the outcome and do not wish to adopt it as a standard practice:

const updateTiming: SideEffect = (inputEvent: Event, run) => {
  const event = inputEvent as EventWithMetadata;   // <- This workaround is not ideal.
  log.debug('updateTiming: %j', event);
  return {
    lastUpdated: event.timestamp,
    deviceId: event.deviceId,
  };
};

Is there a better approach than using casting?

Answer №1

If you require updateTiming to specifically handle EventWithMetadata, then the type of updateTiming must be aware of EventWithMetadata. To ensure that SideEffect can work with other types of Event while knowing about EventWithMetadata, it should ideally be generic in the particular subtype of Event it handles:

type SideEffect<T extends Event> =
    (event: T, run?: Run) => Partial<Run>;

Therefore, updateTiming becomes a

SideEffect<EventWithMetadata>
and not just a simple SideEffect:

const updateTiming: SideEffect<EventWithMetadata> = (event, run) => {
    console.debug('updateTiming: %j', event);
    return {
        lastUpdated: event.timestamp,
        deviceId: event.deviceId,
    };
}; // okay

It is important to note that holding onto a SideEffect without knowledge of what it handles is not possible. This restriction ensures that the correct Event is passed to it. If you believe you need a SideEffect[] without a specific argument type, then a refactoring is required to differentiate events and event handlers using tests (preferably through discriminated unions for both handlers and events, or by implementing custom type guard functions). However, this topic goes beyond the scope of the current question.

Playground link to code

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

Working with Angular2: Linking dropdown values with any number of items

Is there a way to dynamically bind drop down values with numbers from 1 to 100 using a loop in Angular2? I am currently using Ngprime dropdown for a limited number of values, but how can I achieve this for any number of values? Here is the template: < ...

retrieve a shared string from an array when toggled

Regarding the use of SelectionModel for mat-checkbox, a function is called on each click: toggleSelection(row) { this.selection.toggle(row); console.log("Selection"); console.log("this", this.selection.selected); this.selection.selected.f ...

Tips on incorporating toggle css classes on an element with a click event?

When working with Angular typescript instead of $scope, I am having trouble finding examples that don't involve $scope or JQuery. My goal is to create a clickable ellipsis that, when clicked, removes the overflow and text-overflow properties of a spec ...

Duplicate the ng-template using ng-content as the body (make a duplicate of ng-content)

I have been working on creating a feature that allows users to add custom columns to a PrimeNg table. The main reason for developing this feature is to provide users with a default table that already has numerous configuration options pre-set. However, I ...

Extending Error object disrupts `instanceof` validation in TypeScript

Could someone clarify why the error instanceof CustomError part of the code below returns false? class CustomError extends Error {} const error = new CustomError(); console.log(error instanceof Error); // true console.log(error instanceof CustomError); ...

Typescript - neglecting a package that lacks typings

I am considering using an open source package that does not have TypeScript bindings. After checking the linked resource, I was unable to find a solution. Although I attempted to use @ts-ignore, it did not function as expected. Could someone please prov ...

Error: Unable to set value, val.set is not a defined function for this operation (Javascript

Encountering a problem while running the function val.set(key, value), resulting in a type error TypeError: val.set is not a function within the file vendor-es2015.js. Here's the simplified code snippet: import { Storage } from '@ionic/storage& ...

The Typescript errors is reporting an issue with implementing the interface because the type 'Subject<boolean>' is not compatible with 'Subject<boolean>'

Creating an Angular 2 and Typescript application. I am facing an issue with an abstract class within an NPM package that I am trying to implement in my app code. Everything was functioning correctly until I introduced the public isLoggedIn:Subject<bool ...

What is the best way to export Class methods as independent functions in TypeScript that can be dynamically assigned?

As I work on rewriting an old NPM module into TypeScript, I encountered an intriguing challenge. The existing module is structured like this - 1.1 my-module.js export function init(options) { //initialize module } export function doStuff(params) { ...

Implementing a Set polyfill in webpack fails to address the issues

Encountering "Can't find variable: Set" errors in older browsers during production. Assumed it's due to Typescript and Webpack leveraging es6 features aggressively. Shouldn't be a problem since I've successfully polyfilled Object.assign ...

"Define a TypeScript function type that can return either an object or a string depending on whether it succeeds or fails

I encountered an issue with a function that either returns a Promise on success or a string on error. async create(createDebtorDto: CreateDebtorDto): Promise<Debtor> { console.log("createDebtorDto", createDebtorDto) try{ const createdD ...

Images are failing to render on Next.js

Hello there! I am facing an issue while working on my Next.js + TypeScript application. I need to ensure that all the images in the array passed through props are displayed. My initial approach was to pass the path and retrieve the image directly from the ...

Dealing with a void method in a React unit test by treating it as an object's property

How can I pass a value to a function in an interface to trigger a click event on an element? I have a React component that I want to write unit tests for: const VariantItem = (props: VariantItem): ReactElement => { return ( <div key={props.produc ...

Finding the IP address of a server in Angular: A Comprehensive Guide

Is there a way to dynamically retrieve the server host IP address in an Angular application launched with ng serve --host 0.0.0.0? This IP address will be necessary for communication with the backend server. As each coworker has their own unique IP addres ...

Having trouble with gsap.reverse() not functioning properly when using onMouseLeave event in React

I've been incorporating simple gsap animations into my React application. I have successfully triggered an animation.play() function on onMouseEnter, but for some reason, the animation.reverse() function is not functioning as expected. Here's ho ...

Broaden an inaccurate Typescript class declaration

In my project, I am utilizing the NPM package called next-routes. The default export from this package is a class with a specific type definition: export default class Routes implements Registry { getRequestHandler(app: Server, custom?: HTTPHandler): HT ...

Tips for dismissing loader in Ionic 4

I'm struggling to programmatically dismiss the loader. I've developed two separate methods - one for displaying the loader and another for dismissing it. These methods are called accordingly when needed. async showLoader() { this.loader = a ...

ConcatMap in RxJS processes only the last item in the queue

Currently, I am implementing NGRX with RXJS. Within the effects layer, I am utilizing a concatMap to organize my requests in a queue fashion. However, once the latest request is finished, I aim to execute the most recent item added to the queue instead of ...

Tips for adding a border to a table cell without disrupting the overall structure of the table

Looking for a way to dynamically apply borders to cells in my ng table without messing up the alignment. I've tried adjusting cell width, but what I really want is to keep the cells' sizes intact while adding an inner border. Here's the sim ...

What is the process for setting up a Quill Editor within an Angular 2 Component?

I am currently working on creating my own Quill editor component for my Angular 2 project. To integrate Quill into my project, I utilized npm for installation. My goal is to develop a word counter application using this component and I am referring to the ...