Creating recursive interfaces in TypeScript allows for defining reusable structures that reference themselves within

Defining a Modular Type System

Struggling to create a modular type system that meets my needs in an efficient manner. The core issue revolves around needing two variations of the same type system for frontend and backend purposes. This requirement stems from the necessity to derive one representation from the other.

The model can be comprehended as follows:

  • A User possesses multiple Teams.
  • A Team comprises multiple Units.

This encapsulates the conceptual essence denoted by (A):

type IUser = {
    username: string,
    teams: ITeam[],
};

type ITeam = {
    teamname: string,
    user: IUser ,
    units: IUnit[],
}

type IUnit = {
    unitname: string,
    team: ITeam ,
}

The crucial aspect here is the bidirectional traversal between entities, requiring access both upwards and downwards. From User -> Team to Team -> User, facilitating seamless interaction across all levels.

Contextual Clarification

While the above structure aptly caters to frontend functionality, a different depiction is warranted for backend operations. Leveraging TypeORM necessitates rendering these constructs as Entities, leading to code akin to (B) - stripped off any specific TypeORM nuances.

import {Entity, Column, OneToMany, ManyToOne} from "typeorm";

@Entity()
class User extends BaseEntity {
    @Column()
    username: string,

    // A User has many Teams, loaded lazily
    @OneToMany(() => Team, (team) => team.user, {})
    teams: Promise<Team[]>,
};

<Entity>()
// More content goes here...

To conform with TypeORM's guidelines, decisions regarding eager versus lazy loading were discerned. Loading sequences descending down the hierarchy occurs lazily, while upward traversing unfolds eagerly.

Merging Models

The objective remains merging model (B) into model (A) with certain fields undergoing promisification. Verification of this amalgamation would validate the structural coherence.


Identified Dilemma

Challenge surfaces in verifying the correlation between models (A) and (B) due to promisified references intersecting various properties within the structures.


Experimentations Undertaken

An interactive TypeScript playground link delves into disentangling this web of conundrums, concentrating solely on dissecting actual definitions devoid of TypeORM intricacies : Playground link

Incorporated are the aforementioned model representations alongside deliberations on derivation strategies. However, grappling with expressing requisite recursion hinders progress, evident through the completion of the playground exploration.

Answer №1

To tackle this issue effectively, my strategy involves ensuring that both the frontend and backend versions adhere to a shared interface. To accomplish this, I propose the creation of a utility type called MaybePromise<T>, which defines a value as either a promise or a resolved value:

type MaybePromise<T> = T | Promise<T>;

Utilizing this utility type, I revamp the interfaces I to serve as the common base, capable of incorporating promises or non-promised values.

type IUser = {
    username: string,
    teams: MaybePromise<ITeam[]>,
};

type ITeam = {
    teamname: string,
    user: MaybePromise<IUser>,
    units: MaybePromise<IUnit[]>,
}

type IUnit = {
    unitname: string,
    team: MaybePromise<ITeam>,
}

If there is a need to revert to the original non-promised interfaces, it can be achieved through another utility type that substitutes these maybe promises with their resolved values.

type UnpromisifyFields<T> = {
    [key in keyof T]: T[key] extends MaybePromise<infer U> ? UnpromisifyFields<U> : T[key]
}

While this recursive approach may make the resolved types less easily readable, they do appear to be accurate.

type PureUser = UnpromisifyFields<IUser>
// transforms to { username: string; teams: UnpromisifyFields<ITeam>[]; }

type PureTeam = UnpromisifyFields<ITeam>

type PureUnit = UnpromisifyFields<IUnit>

Incorporating the base MaybePromise interfaces into our backend classes is achievable as follows:

class User implements IUser {    
    username!: string;
    teams!: Promise<Team[]>;
};

class Team implements ITeam {
    teamname!: string;
    user!: User;
    units!: Promise<Unit[]>;
}

class Unit implements IUnit {
    unitname!: string;
    team!: Team;
}

This approach appears to address the majority of your concerns effectively.

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 with null parameter in Pulumi's serviceAccountKey.privateKeyData callback

My code in index.ts is as follows: import {Key, ServiceAccount} from "@pulumi/google-native/iam/v1"; const serviceAccountKey = new Key('service-account-key-' + new Date().toISOString(), { serviceAccountId: serviceAccount. ...

What is the best way to link multiple select tags in an HTML document?

I am working with a data grid that contains student information such as Name, Class, and Score. Each row has a checkbox for selection. The requirement is that when the user selects one or more rows and clicks on the "show information" Button, a new windo ...

Develop a Yeoman generator that incorporates API calls

After successfully creating a Yeoman generator, I now have the task of adding two additional questions to it. I already have these questions in place async prompting() { const prompts = [ { name: "appName", message: "Proje ...

Having trouble with the sx selector not functioning properly with the Material UI DateTimePicker component

I'm currently working with a DateTimePicker component and I want to customize the color of all the days in the calendar to match the theme color: <DateTimePicker sx={{ "input": { color: "primary.main&quo ...

What advantages does using 'async' offer for TypeScript functions that already have a Promise return type?

Is there any advantage to declaring a TypeScript function as async if the return type is already a promise? It seems like both methods achieve the same result: function foo(): Promise<Bar> { } //vs async function foo(): Bar { } //Or even async fu ...

Caution: It is important for each child in a list to have a distinct "key" prop when adding a new element to the list

I have been working on a project that involves creating a list of items using React, Express (axios), and MongoDB. The items in the list are retrieved from MongoDB, displayed in a component, and users have the ability to add new items (which are then save ...

Each element in ngFor triggers the invocation of ngAfterContentInit by Angular

In my Angular 11 (Ionic 5) app, I have a scenario with two components: A ContainerComponent and a BoxComponent. Both of these components are completely transparent (template: '<ng-content></ng-content>'). The challenge is for the cont ...

Challenges of Integrating Auth0 with Angular2/.NETCore Project

I am struggling to integrate this Custom Login auth0 service because I am facing issues with the imports. The problem arises specifically with the usage of declare var auth0: any. Every time I attempt to use it, I encounter the following error: EXCEPTION: ...

Using Express middleware in a TypeScript Express application

I'm currently converting the backend of an ExpressJS application to Typescript. While working on the auth.routes.ts file, I encountered an issue with the middleware (authMiddleware). It seems like there might be a typing error, as the same code in the ...

Casting Types in TypeScript

Here is the TypeScript code I am using to create an ApolloClient: return new ApolloClient({ dataIdFromObject: (o) => o.uuid }); Upon compilation, I encountered the following error message: TS2339:Property 'uuid' does not exist on type ...

Two-way data binding in Angular 2 is a powerful feature that allows for

My goal is to construct a parent component called Action, which includes two child components named Infos and Localisation. I want to connect the inputs of the children with the parent model. This is the model: export class Action{ title: string; ...

Is there a way to modify the antd TimePicker to display hours from 00 to 99 instead of the usual 00 to 23 range?

import React, { useState } from "react"; import "./index.css"; import { TimePicker } from "antd"; import type { Dayjs } from "dayjs"; const format = "HH:mm"; const Clock: React.FC = () =& ...

Incorporating the Observable Type into a TypeScript Interface

I want to create a TypeScript interface that looks like this: declare namespace UserService { interface IUserService { // Error: "Observable" can't be found getUsers(): Observable<Array<UserService.IUser>>; } ...

Functions outside of the render method cannot access the props or state using this.props or this.state

Just starting out with react. I've encountered an issue where a prop used in a function outside the render is coming up as undefined, and trying to use it to set a state value isn't working either. I've researched this problem and found va ...

What is the process for creating documentation for a TypeScript enum type with the format of { [key]: value }

I am currently developing a logger service for nodeJS using Typescript. One important component of this project is an enum that looks like this: enum LOG_TYPES { NONE = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4, } Along with the enum, I have i ...

Tips for utilizing <Omit> and generic types effectively in TypeScript?

I'm currently working on refining a service layer in an API with TypeScript by utilizing a base Data Transfer Object. To prevent the need for repetitive typing, I have decided to make use of the <Omit> utility. However, this has led to some per ...

Having trouble with linting on Typescript 3.7 within the Angular 9 tslint environment

After transitioning to Angular version 9 that includes Typescript 3.7, I observed that my tslint is not identifying new features like optional chaining and null coalescing. Should I consider switching to eslint, or is there a solution to address this iss ...

What is the method for passing an element in Angular2 Typescript binding?

Is there a way to retrieve the specific HTML dom element passed through a binding in Angular? I'm having trouble figuring it out, so here is the relevant code snippet: donut-chart.html <div class="donut-chart" (donut)="$element"> ...

Using ESLint to enforce snake_case naming conventions within TypeScript Type properties

When working with TypeScript, I prefer to use snake_case for properties within my Interfaces or Types. To enforce this rule, I have configured the ESLint rule camelcase as follows: 'camelcase': ["error", {properties: "never"}], Even though the E ...

Executing a series of promises sequentially and pausing to finish execution

I have been attempting to run a loop where a promise and its respective then method are created for each iteration. My goal is to only print 'Done' once all promises have been executed in order. However, no matter what I try, 'done' alw ...