Limit function parameters to only accept values with matching keys

I am relatively new to using TypeScript and am currently working on a project that involves handling various shapes of data from different sources. My goal is to pass this data to different aggregator classes, with one aggregator class corresponding to each specific data shape.

In order to achieve this, I believe I need to create a function (referred to as dispatch below) that accepts a generic type of data and an identifier for the associated aggregator class. However, I am facing difficulties in properly constraining the parameters to satisfy TypeScript's requirements.

Here is what I have managed to put together so far:

enum SourceIdentifier {
  A = 'a',
  B = 'b'
}

interface DataA {
  prop1: number,
  prop2: number[],
}

interface DataB {
  prop3: number,
  prop4: number,
}

class BaseAggregator<Data> {
  public add(data: Data) {
    // Store the data to be aggregated.
  }
}

class AggregatorA extends BaseAggregator<DataA> {}
class AggregatorB extends BaseAggregator<DataB> {}

const managers = {
  [SourceIdentifier.A]: new AggregatorA(),
  [SourceIdentifier.B]: new AggregatorB()
}

// How can I correctly constrain this function?
const dispatch = (data, source: SourceIdentifier) => {
  managers[source].add(data);
};

I have attempted to utilize generics, but I keep encountering a TypeScript error:

const dispatch = <Data, Manager extends BaseAggregator<Data>>(data: Data, source: SourceIdentifier) => {
  /*
   * Type 'AggregatorA | AggregatorB' is not assignable to type 'Manager'.
   *   'Manager' could be instantiated with an arbitrary type which could be unrelated to 'AggregatorA | AggregatorB'
   */
  const manager: Manager = managers[source];
  manager.add(data);
};

Is it feasible to properly constrain a function like this, or am I facing limitations due to the absence of types at runtime? I would greatly appreciate any assistance. Thank you.

Answer №1

Your approach to using generics in the dispatch function was on point. To narrow down the types that a generic can be, you can utilize the union operator (|).

For example, by specifying

<Data extends DataA | DataB, ...>
in your generics declaration, you are restricting the type Data to be either DataA, DataB, or a combination of both. This same technique can be applied to the generic in your BaseAggregator class.

Answer №2

After incorporating discriminators into my code, I was able to achieve the desired outcome using the following approach:

enum CategoryIdentifier {
  X = 'x',
  Y = 'y'
}

interface DiscriminatedCategory {
  discriminator: CategoryIdentifier;
}

interface CategoryX extends DiscriminatedCategory {
  discriminator: CategoryIdentifier.X,
  attribute1: number,
  attribute2: number[],
}

interface CategoryY extends DiscriminatedCategory {
  discriminator: CategoryIdentifier.Y,
  attribute3: number,
  attribute4: number,
}

type MessageData = CategoryX | CategoryY;

class BaseCollector<Data> {
  public collect(data: Data) {
    // Logic to aggregate and store the data.
  }
}

class CollectorX extends BaseCollector<CategoryX> {}
class CollectorY extends BaseCollector<CategoryY> {}

const repositories = {
  [CategoryIdentifier.X]: new CollectorX(),
  [CategoryIdentifier.Y]: new CollectorY()
}

const distribute = (data: MessageData) => {
  const repository: BaseCollector<unknown> = repositories[data.discriminator];
  repository.collect(data);
};

// Sample implementation
distribute({discriminator: CategoryIdentifier.X, attribute1: 8, attribute2: [5, 10]})

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

Encountering a WriteableDraft error in Redux when using Type Definitions in TypeScript

I'm facing a type Error that's confusing me This is the state type: export type Foo = { animals: { dogs?: Dogs[], cats?: Cats[], fishs?: Fishs[] }, animalQueue: (Dogs | Cats | Fishs)[] } Now, in a reducer I&a ...

Encountering the error message "Uncaught Promise (SyntaxError): Unexpected end of JSON input"

Below is the code snippet I am using: const userIds: string[] = [ // Squall '226618912320520192', // Tofu '249855890381996032', // Alex '343201768668266496', // Jeremy '75468123623614066 ...

The function for batch insertion only functions with Postgresql and SQL Server databases

I am a beginner in JavaScript and I am currently working on creating a new restaurant. I have come across a code snippet that inserts a relation into a join-table: await newRestaurant.$relatedQuery('tags', trx).relate(tagIds); Is it not possible ...

Simulating service calls in Jest Tests for StencilJs

When testing my StencilJs application with Jest, I encountered an issue with mocking a service class method used in a component. The service class has only one function that prints text: The Component class: import {sayHello} from './helloworld-servi ...

Tally up identical words without considering differences in capitalization or extra spaces

Let's take an example with different variations of the word "themselves" like "themselves", "Themselves", or " THEMSelveS " (notice the leading and trailing spaces), all should be considered as one count for themselves: 3 ...

Typescript's identification of a dispute between RequireJS and NodeJS definitions

I obtained the Typescript RequireJS definition from Definitely Typed. It includes an ambient declaration of Require that clashes with the NodeJs command "require". See below for the declaration and the error message: Declaration: declare var require: Req ...

eliminate any redundant use of generics

Recently, I attempted to create a pull request on GitHub by adding generics to a method call. This method passes the generically typed data to an interface that determines the return type of its methods. However, the linter started flagging an issue: ERR ...

How to dynamically retrieve values from a const object literal using TypeScript

Currently, I am utilizing a TypeScript library known as ts-proto, which is responsible for generating TypeScript code. The resulting generated code resembles the following: //BasicMessage.ts export interface BasicMessage { id: Long; name: string; } ...

Steps for utilizing Bazel to compile TypeScript

Calling all Bazel (Blaze) experts: I'm curious about the best method for integrating Bazel as a build system for cutting-edge web applications built in Typescript. Is there a preferred setup or perhaps a template that demonstrates this integration? T ...

Is there a solution for resolving the 'cannot post error' in nodejs?

Recently started using node.js I am currently working on a nodejs-experss-mongodb project and I am in the process of implementing a subscription feature that has the following specific requirements: Request Method: POST URL: localhost:8080/api/v1/users/: ...

Why does HttpClient in Angular 4 automatically assume that the request I am sending is in JSON format?

Currently, I am working with Angular 4's http client to communicate with a server that provides text data. To achieve this, I have implemented the following code snippet: this.http.get('assets/a.txt').map((res:Response) => res.text()).s ...

Setting up VSCode to run various tasks

My TypeScript project in Visual Studio Code has a specific task outlined as follows: { "version": "0.1.0", // The command is tsc. "command": "tsc", // Show the output window only if unrecognized errors occur. "showOutput": "silent", // Und ...

What is the method for adding local images to FormData in Expo version 48 and above?

When working with Expo v47 and its corresponding React Native and TypeScript versions, FormData.append had the following typing: FormData.append(name: string, value: any): void An example of appending images using this code could be: const image = { uri ...

Combine two elements in an array

I am faced with a challenge in binding values from an Array. My goal is to display two values in a row, then the next two values in the following row, and so on. Unfortunately, I have been unable to achieve this using *ngFor. Any assistance would be greatl ...

include choices to .vue document

When looking at Vue documentation, you may come across code like this: var vm = new Vue({ el: '#example', data: { message: 'Hello' }, template: `<div> {{ message }} </div>`, methods: { reverseM ...

JavaScript Class Emit Signal for establishing a sequence of interconnected events

My Vue project includes a JavaScript class specifically for mobile devices. I'm looking to have this class emit a signal once the property 'hasEnded' is set to True for my object. How can I achieve this and chain together other events based ...

External function does not support jQuery types

In my theme.js file, I currently have the following code: jQuery(function ($) { accordion($) }) const accordion = ($) => ... By placing the accordion function directly into the jQuery function, Typescript is able to assist with the installed jquery ...

What steps should I take to correctly identify the type in this specific situation?

Let's consider the function f, defined as follows: function f<T extends Fields = Fields>(props: Props<T>) { return null; } In this context, T represents a generic type that extends Fields. The concept of Fields is captured by the follow ...

Vue-i18n does not offer a default export option

Hello everyone! This is my first experience using vue-i18n in a project with TypeScript + Vue. Following the instructions from the official site, I installed it using yarn install vue-i18n. Next, I tried to import it into main.ts using import VueI18n from ...

Tips for sending a parameter to an onClick handler function in a component generated using array.map()

I've been developing a web application that allows users to store collections. There is a dashboard page where all the user's collections are displayed in a table format, with each row representing a collection and columns showing the collection ...