Leverage the generic parameter type inferred from one function to dynamically type other functions

I am in the process of developing an API for displaying a schema graph.

Here is a simplified version of what it entails:

interface Node {
  name: string;
}

type NodeNames<T extends Node[]> = T[number]["name"]; // Union of all node names as strings

type Scalars = 'Int' | 'Boolean' | 'String'

type AllTypes<T extends Node[]> = NodeNames<T> | Scalars

interface Schema<T extends Node[]> {
  createNode(name: String, fields: Record<string, AllTypes<T>>): Node;

  render(types: T[]): string;
}

const s: Schema = { 
  render(types) {
    return ''
  },
  createNode(name, fields) {
    return { name, fields };
  }
};

const Blog = s.createNode("Blog", { users: "User" });
const User = s.createNode("User", { posts: "Post" });
const Post = s.createNode("Post", { title: "String", comments: 'Comment' }); 

s.render([Blog, User, Post]);

I want to ensure that types not registered in the Schema.render cannot be referenced in the Schema.createNode function.

In the provided example, the type of fields should be:

Record<string, Scalars | 'User' | 'Post' | 'Blog'
, where User | Post | Blog is derived from the nodes passed to s.render.

To achieve this, I aim to utilize the inferred node names from render function to define the values of the fields parameter in Schema.createNode.

However, it seems tricky as generics are only captured at the function level and not at the interface level. Is there a way to leverage the inferred generic of

s.render<T extends Node[]>(nodes: T)
to specify the function
createNode(name: string, fields: AllTypes<T>)
with the same shared inferred type?

Any suggestions on how to make this work, even if the API needs slight modifications, would be greatly appreciated.

Thank you 🙏

Answer №1

To handle multiple calls efficiently, it is recommended to design a class that can accept schema definitions as parameters and evaluate the entire object at once:

interface Node<T extends string = string> {
    name: T;
}

type Scalars = 'Int' | 'Boolean' | 'String'

type AllTypes<T extends Record<keyof T, any>> = keyof T | Scalars
type Values<T> = T[keyof T]

type SchemNodes<T extends Record<keyof T, Record<string, AllTypes<T>>>> =
    { [P in Extract<keyof T, string>]: Node<P> }

class Schema<T extends Record<keyof T, Record<string, AllTypes<T>>>> {
    constructor(types: T) { }
    getNodes(): SchemNodes<T> {
        return null!;
    }
    renderTypes(types: Array<Values<SchemNodes<T>>>) { }
}

const s = new Schema({
    "User": { posts: "Post" },
    "Blog": { users: "User" },
    // "Comment": {},
    "Post": { title: "String", comments: 'Comment' }
})

const { Blog, Post, User } = s.getNodes();

Play

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

How can I detect if a control value has been changed in a FormGroup within Angular 2? Are there any specific properties to look

I am working with a FormGroup that contains 15 editable items, including textboxes and dropdowns. I am looking to identify whether the user has made any edits to these items. Is there a specific property or method I can use to check if the value of any i ...

Upon initial loading, the default sidenav in Angular is not selected

I need to ensure that the sidenav loads the basic page during the initial load. This is controlled by the routing.ts file shown below: import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; i ...

Displayed even when data is present, the PrimeNg empty message persists

I have set up a PrimeNg table to display data with an empty message template like this: <ng-template pTemplate="emptymessage"> <tr> <td> No records found </td> </tr> </ng-template> ...

transferring data between components in a software system

I received a response from the server and now I want to pass this response to another component for display. I attempted one method but ended up with undefined results. You can check out how I tried here: how to pass one component service response to other ...

Jest is simulating a third-party library, yet it is persistently attempting to reach

Here's a function I have: export type SendMessageParameters = { chatInstance?: ChatSession, // ... other parameters ... }; const sendMessageFunction = async ({ chatInstance, // ... other parameters ... }: SendMessageParameters): Promise<vo ...

Search through the directory of images and generate a JSON representation

I'm looking for a way to transform a URL-based directory of images into a Json object, which I can then utilize and connect within my Ionic project. Despite extensive searching, I have yet to find any satisfactory solutions to this challenge. Thus, I ...

How to access class type arguments within a static method in Typescript: A clever solution

An issue has arisen due to the code below "Static members cannot reference class type parameters." This problem originates from the following snippet of code abstract class Resource<T> { /* static methods */ public static list: T[] = []; ...

Using Typescript literal types as keys in an indexer is a common practice

Can we create a TypeScript literal type that acts as a string key in an indexer? type TColorKey = 'dark' | 'light'; interface ColorMap { [period: TColorKey]: Color; } But when attempting this, the following error is generated: An ...

Maintaining the selected option on page refresh with React Remix

I have a dropdown menu with 2 choices (en, no) for switching the language when onChange event occurs. To save the selected language, I am using localStorage. Due to the server-side rendering in Remix, direct access to localStorage is not possible. Therefo ...

The transition() function in Angular 2.1.0 is malfunctioning

I am struggling to implement animations in my Angular 2 application. I attempted to use an example I found online and did some research, but unfortunately, I have not been successful. Can anyone please assist me? import {Component, trigger, state, anima ...

What is the significance of requiring a specific string in a Typescript Record when it is combined with a primitive type in a union?

I am facing an issue with the following data type: type ErrorMessages = Record<number | 'default', string>; When I declare a variable like const text: ErrorMessages = {403: 'forbidden'}, Typescript points out that default is miss ...

Leverage context to facilitate communication between components operating at various levels of the system

I am currently working on the settings pages of my applications. Each page features a common SettingsLayout (parent component) that is displayed across all settings pages. One unique aspect of this layout is the presence of an ActionsBar, where the submit/ ...

Has the GridToolbarExport functionality in Material UI stopped working since the latest version update to 5.0.0-alpha.37?

I have created a custom toolbar for my Data Grid with the following layout: return ( <GridToolbarContainer> <GridToolbarColumnsButton /> <GridToolbarFilterButton /> <GridToolbarDensitySelector /> <Gr ...

Following the build in Angular, it only displays the index.html file and a blank screen

According to the information on Angular's official website, running the "ng build" command should generate files in the dist folder ready for hosting. However, after running this command, the index.html file is empty except for the page title. When yo ...

Troubleshooting: Vue and TypeScript Components Not Communicating

Vue is still fairly new to me and I'm struggling with this issue. Despite following code examples, my implementation doesn't seem to be working correctly. The component I defined looks like this: <template> <div class="row"> ...

A helpful guide on integrating a Google font into your Next.js project using Tailwind CSS locally

I'm planning to use the "Work Sans" Font available on Google Fonts for a website I'm working on. After downloading the "WorkSans-Black.ttf" file, I created a subfolder named "fonts" within the "public" folder and placed the font file in there. Be ...

Typescript: The dilemma of losing the reference to 'this'

My objective is to set a value for myImage, but the js target file does not contain myImage which leads to an error. How can I maintain the scope of this within typescript classes? I want to load an image with the Jimp library and then have a reference to ...

Using Angular NgRx - triggering an action from an effect once certain actions yield a result

I'm encountering difficulties in dispatching actions that require results from five other actions (all listed in my effect). Could someone lend a hand? Essentially, I need to trigger an action within the effect only after these five actions have retu ...

Limitations require a member to only accept a type (and not an instance) that extends or implements another type [TypeScript]

I'm seeking assistance with a set of abstract concepts in TypeScript. I am looking to restrict a member to only accept types as values, but those types must also implement or extend other types or interfaces. For example: The code snippet below is ...

Determine the generic types of callback in TypeScript based on the argument provided

There are numerous Stack Overflow questions that share a similar title, but it seems none of them address this particular inquiry. I'm in the process of developing a wrapper for an express RequestHandler that can catch errors in asynchronous handlers ...