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

Angular authentication guard does not redirect properly after returning a Promise<UrlTree>

My authentication guard is set up to control access to the /sign-in and /verify-email routes, allowing only users who are not logged in or have not verified their email: export const signInGuard: CanActivateFn = (activatedRouteSnapshot: ActivatedRouteSnap ...

Is it possible to utilize a partial entity for saving with TypeORM?

My current table structure looks like this: --changeset 0004-order:ccushing create table if not exists "order"."order" ( id uuid primary key not null default uuid_generate_v4(), state uuid re ...

Failed to decipher an ID token from firebase

I'm feeling extremely frustrated and in need of assistance. My goal is to authenticate a user using Google authentication so they can log in or sign up. Everything worked perfectly during development on localhost, but once I hosted my app, it stopped ...

MUI: Transforming the uncontrolled value state of Select into a controlled one with a new component

I'm attempting to develop an edit form for modifying data fetched from a database based on its ID. Here is what I have tried: import React, {FormEvent, useEffect, useState} from "react"; import TextField from "@material-ui/core/ ...

Navigating a page without embedding the URL in react-router-dom

In my application, I am utilizing react-router-dom v5 for routing purposes. Occasionally, I come across routes similar to the following: checkup/step-1/:id checkup/step-2/:id checkup/step-3/:id For instance, when I find myself at checkup/step-1/:id, I int ...

Is there a specific type required for the Vue `install` function? Is it necessary to have the `directive` property on the `Vue`

I've been working on implementing a Vue directive in typescript, but I'm struggling to determine the correct types to use for the Vue install function. Using install(Vue: any): void feels a bit strange to me. I attempted importing Vue and using ...

The intricacies of Mongoose schemas and virtual fields

I'm currently working on a NodeJS project using TypeScript along with Mongoose. However, I encountered an issue when trying to add a virtual field to my schema as per the recommendations in Mongoose's documentation. The error message stated that ...

What's the best way to import a file from OneDrive to an Angular app using Typescript?

I am currently working on an Angular application that utilizes OneDrive/Sharepoint for file storage. Authentication is functioning properly, and I can successfully save files. However, I am encountering an issue when attempting to download a file created a ...

The issue with Angular's Array.push method arises when only the last object in the array is being pushed after a mat-checkbox

I am currently working with two components called item-list and item. The item-list component is responsible for displaying a list of items with checkboxes. I have been facing an issue where the Array that stores the checked items only retains the last ite ...

When logging `self`, the output field is present; however, attempting to log `self.output` results in

I've encountered a strange issue. When I use console.log(self) to log the variable, it shows that the output key is set and contains all the values. However, if I try to log console.log(self.output), it returns undefined. Does anyone know why this is ...

A guide on applying color from an API response to the border-color property in an Angular application

When I fetch categoryColor from the API Response, I set border-left: 3px solid {{element.categoryColor}} in inline style. Everything is functioning correctly with no development issues; however, in Visual Studio, the file name appears red as shown in the i ...

The functionality of Angular's mat-autocomplete is hindered when it comes to utilizing options generated by a function

I decided to enhance the autocomplete feature on my project by taking an example from the Material official website. Instead of having the options stored in a variable within the component class, I created a function to retrieve the options. Although the o ...

What are some ways to leverage a promise-returning callback function?

Here is a function that I have: export const paramsFactory = (params: paramsType) => { return ... } In a different component, the same function also contains await getPageInfo({ page: 1 }) after the return .... To make this work, I need to pass a cal ...

Is there a way to trigger the click event in the week view of an Angular 2+ calendar?

https://i.sstatic.net/Vx2x8.png HTML Templates <mwl-calendar-week-view [viewDate]="viewDate" [refresh]="refresh" (click)="weekDayClick($event)"> </mwl-calendar-week-view> In the component file weekDayCl ...

Using TypeScript to narrow down types within mapped types

Can you create a mapped type based on the property type? For example, if I want to map all properties with type String to Foo and all other types to Bar. Can this be done like this: type MappedType<T> = { [P in keyof T]: T[P] === String ? Foo : B ...

Leveraging the 'ref' keyword in TypeScript with Next.js

Currently, I am learning TypeScript in React but encountered a warning. import {useref} from 'react' export default function test(){ cons tmp = useRef() const data = tmp.current?.value return ( <div> <input type = ...

Tips for streamlining a conditional statement with three parameters

Looking to streamline this function with binary inputs: export const handleStepCompletion = (userSave: number, concur: number, signature: number) => { if (userSave === 0 && concur === 0 && signature === 0) { return {complet ...

Ways to retrieve data object within an HTMLElement without relying on jQuery

Within my web application, I have successfully linked a jQuery keyboard to a textbox. Now, I am seeking a way to explicitly call the keyboard.close() function on the keyboard as I am removing all eventListeners from the text field early on. To achieve thi ...

Troubleshooting issues with TypeScript D3 v4 module import functionality

As I embark on the journey of creating a miniature JS library using D3 to visualize line charts, I find myself navigating unfamiliar waters. However, I believe that deep diving into this project is the most effective way for me to learn. Below is the cont ...

How can I extract a value from an object that is readonly, using a formatted string as the key?

I encountered a situation where I have code resembling the following snippet. It involves an object called errorMessages and multiple fields. Each field corresponds to various error messages in the errorMessages object, but using a formatted string to retr ...