Generate a TypeScript generic function that maps class member types to class types

I am dealing with the following function in my codebase

export async function batchEntitiesBy<Entity, T extends keyof Entity>(
  entityClass: EntityTarget<Entity>
  by: T,
  variables: readonly Entity[T][]
) {
    by: T,
  variables: readonly Entity[T][]
) {
  // retrieve entities from the database without grouping and in random order
  const entities = await db.find(entityClass, { [by]: In(variables as Entity[T][]) })

  // group the entities and order the groups based on variables order
  type EntityMap = { [key in Entity[T]]: Entity[]}

  const entityMap = {} as EM;
  entities.forEach((e) => {
    if (!entityMap[e[by]]) {
       entityMap[e[by]] = []
    }
     entityMap[e[by]].push(e)
  })
  return variables.map((v) => entityMap[v]);
}

I anticipate that Entity[T] will provide me with the type of the class member specified in by, making entityMap a mapping from type(by) to type(Entity)

Why am I encountering this error??

Type 'Entity[T]' is not assignable to type 'string | number | symbol'.
  Type 'Entity[keyof Entity]' is not assignable to type 'string | number | symbol'.
    Type 'Entity[string] | Entity[number] | Entity[symbol]' is not assignable to type 'string | number | symbol'.
      Type 'Entity[string]' is not assignable to type 'string | number | symbol'.

Edit:

If we take an example entity

class ExampleEntity {
  a: string,
  b: number
}

My expectations are:

  • by should be either a or b
  • If by is a, then I would expect Entity[T] to be string

referring to TypeScript documentation https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types

Here demonstrating the same issue in playground


Edit2:

Below are some sample entities I intend to utilize with this function:

class User {
  id: string
  name: string
  address: Address
  addressId: number
}

class Address {
  id: number
  street: string
  num: number
}

example usage:

const adrIds = [1,5,2,9,4]

const users = batchEntitiesBy<User, addressId>(Users, "addressId", adrIds)

Answer №1

Summarizing the progress based on the revised question and chat discussion, an additional challenge arose from the fact that the original code utilized the values of Entity as keys in an object (EntityMap), but not all values of Entity were valid keys (specifically Address). Switching to a Map instead of an object provided a solution for this issue.


The issue in your code seems to stem from the indecision between using the generic Entity or ExampleEntity.

In the following code snippet, I consistently used ExampleEntity:

type ExampleEntity = {
    a: string,
    b: number
}

const batchExampleEntitiesBy = async <By extends keyof ExampleEntity>(
    by: By, variables: readonly ExampleEntity[By][]
) => {
    const entityMap = {} as { [key in ExampleEntity[By]]: ExampleEntity[] }

    const entities = [
        { a: 'hello', b: 1 },
        { a: 'world', b: 2 },
        { a: 'hello', b: 3 },
        { a: 'world', b: 4 },
    ] as ExampleEntity[];

    entities.forEach((e) => {
        if (!entityMap[e[by]]) {
            entityMap[e[by]] = []
        }
        entityMap[e[by]].push(e)
    })

    return variables.map((v) => entityMap[v]);
};


const foo = batchExampleEntitiesBy("a", ["hello"])

In the next code excerpt, I parameterized ExampleEntity:

type ExampleEntity = {
    a: string,
    b: number
}

type Key = string | number | symbol;

const batchEntitiesBy = <Entity extends Record<Key, Key>>() =>
    async <By extends keyof Entity>(by: By, variables: readonly Entity[By][]) => {
        const entityMap = {} as { [key in Entity[By]]: Entity[] }

        const entities = [
            { a: 'hello', b: 1 },
            { a: 'world', b: 2 },
            { a: 'hello', b: 3 },
            { a: 'world', b: 4 },
        ] as unknown as Entity[];

        entities.forEach((e) => {
            if (!entityMap[e[by]]) {
                entityMap[e[by]] = []
            }
            entityMap[e[by]].push(e)
        })

        return variables.map((v) => entityMap[v]);
    };

const batchExampleEntitiesBy = batchEntitiesBy<ExampleEntity>()

const foo = batchExampleEntitiesBy("a", ["hello"])

I anticipate that entities will likely be fetched from a server. If you opt for the parameterized version, it's advisable to align the fetching behavior with the asserted type. Additionally, runtime type checking of the server response is recommended.

Personally, I would convert this function to synchronous and expect entities as an input.

const entities = [
    { a: 'hello', b: 1 },
    { a: 'world', b: 2 },
    { a: 'hello', b: 3 },
    { a: 'world', b: 4 },
]

type Key = string | number | symbol;

const batchEntitiesBy = <
    Entity extends Record<Key, Key>,
    By extends keyof Entity
>(
    entities: Entity[],
    by: By,
    variables: readonly Entity[By][]
) => {
        const entityMap = {} as { [key in Entity[By]]: Entity[] }

        entities.forEach((e) => {
            if (!entityMap[e[by]]) {
                entityMap[e[by]] = []
            }
            entityMap[e[by]].push(e)
        })

        return variables.map((v) => entityMap[v]);
    };


const foo = batchEntitiesBy(entities, "a", ["hello"])

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

Generic partial application fails type checking when passing a varargs function argument

Here is a combinator I've developed that converts a function with multiple arguments into one that can be partially applied: type Tuple = any[]; const partial = <A extends Tuple, B extends Tuple, C> (f: (...args: (A & B)[]) => C, ...a ...

Ensure that the method is passed a negative number -1 instead of the literal number 1 from an HTML error

This is an example of my HTML code: <button (mousedown)="onMouseDown($event, -1)"></button> Here is the TypeScript code for handling the mouse down event: onMouseDown(e: MouseEvent, direction: 1|-1) { this.compute.emit(direction); ...

Exploring the process of linking MatPaginator to a server-sourced datasource within an Angular Material table

In my Angular 5 project, I am utilizing Angular Material table to create a grid. The data is fetched from an HTTP request and stored in a variable named dataSourceNew within the view.ts file. Since dataSourceNew has dynamic content and structure, no interf ...

Is it possible to define a constant enum within a TypeScript class?

I am looking for a way to statically set an enum on my TypeScript class and be able to reference it both internally and externally by exporting the class. As I am new to TypeScript, I am unsure of the correct syntax for this. Below is some pseudo-code (whi ...

Converting an array of arrays into an object with an index signature: A step-by-step guide

I find myself facing a challenge where I have two types, B and A, along with an array called "a". My objective is to convert this array into type B. Type A = Array<[string, number, string]>; Type B = { [name: string]: { name: ...

What is the correct way to construct an object in TypeScript while still taking types into account?

Hey, I'm having trouble implementing a common JavaScript pattern in TypeScript without resorting to using any to ignore the types. My goal is to write a function that constructs an object based on certain conditions and returns the correct type. Here& ...

Why is my data not showing correctly? - Utilizing Ionic 3 and Firebase

I'm experiencing a challenge with displaying Firebase data in my Ionic 3 application. Below is the relevant snippet of code from my component where 'abcdef' represents a placeholder for a specific user key: var ref = firebase.database().ref ...

Is there a way to determine the height of the ion-footer?

Is there a way to obtain the height of the ion-footer element in Ionic2? I want to calculate the initial window height minus the ion-footer height, but I am currently only able to get the overall window height. I'm not interested in retrieving the ...

Checking conditions sequentially in Angular

I have a unique use case that requires me to verify certain conditions. If one condition fails, I should not proceed to the next one. Instead, based on the failed condition, I need to display a dialog with a title and description explaining what went wrong ...

Encountering a Typescript TypeError in es2022 that is not present in es2021

I'm attempting to switch the target property in the tsconfig.json file from es2015 to es2022, but I am encountering an error while running tests that seem to only use tsc without babel: Chrome Headless 110.0.5481.177 (Mac OS 10.15.7) TypeError: Can ...

TypeScript perplexed Babel with its unfamiliar syntax and could not compile it

Encountered a problem while attempting to compile typescript. It appears that babel was unable to comprehend the "?." syntax on the line node.current?.contains(event.target) export function useOnClickOutside(node: any, handler: any) { const handlerRef = ...

What is the process of adding an m4v video to a create-next-app using typescript?

I encountered an issue with the following error: ./components/Hero.tsx:2:0 Module not found: Can't resolve '../media/HeroVideo1-Red-Compressed.m4v' 1 | import React, { useState } from 'react'; > 2 | import Video from '../ ...

Improving the management of user input in Lit components

I am seeking a more efficient method to manage all inputs within my lit component while minimizing the code usage. I am also employing Typescript in this process, utilizing @change=${this.handleInput} for all input fields. Below is an example of how the ha ...

Create duplicates of both the array and its individual elements by value

I'm having trouble figuring out how to properly copy an array along with all its elements. In my model, I have a name and options, both of which are strings. This is what I currently have: const myArrayToDuplicate = [myModel, myModel, myModel] My ...

Querying the api for data using Angular when paginating the table

Currently, I have a table that retrieves data from an API URL, and the data is paginated by default on the server. My goal is to fetch new data when clicking on pages 2, 3, etc., returning the corresponding page's data from the server. I am using an ...

Utilizing ES6 class methods as a parameter for Express routing

I'm having trouble passing a class method as an Express route parameter. I've attempted to bind the method and also tried using arrow functions, but neither approach has worked for me. My project involves TypeORM, and I keep encountering the err ...

Methods to validate CSS attributes specified within a class using React testing library

I am struggling to validate the CSS properties defined within a class in CSS using the React Testing Library. Unfortunately, I am facing challenges in doing so. Here are the simplified code snippets: import React from "react"; import { render, ...

The defineProps<SomePropType>() method is not rendering the props as expected

I have various components, with a parent element where I attempted to pass props using the syntax defineProps<{}>(). The setup is simple, I have a types.ts file at the root level, a parent Vue file (referred to as CardItem), and multiple components. ...

Guide to resolving the issue of error Type 'void[] | undefined' cannot be assigned to type 'ReactNode'

I am attempting to map the data Array but I am encountering an error: Type 'void[] | undefined' is not assignable to type 'ReactNode'. Can someone please assist me in identifying what I am doing wrong here? Below is the code snippet: i ...

Is it possible to measure the CPU utilization in a TypeScript application programmatically?

Is there a method to calculate CPU usage as a percentage and record it in a file every 20 milliseconds? I'm interested in exploring different approaches for accomplishing this task. Your insights would be greatly appreciated! I've come across so ...