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 🙏