'Is Typescript able to force the domain of a value to be within a set of key names declared in the same object?

I am quite new to Typescript and I am really appreciating its type checking capabilities.

I need to understand if with its capabilities it is possible to check at compile time if the domain of a value is within a set of key names declared in the same object?

The following example will help to clarify my question. I have these object and type definitions:

type ElemField = { name: string }
type Elem = Record<string, ElemField>

type Relation = { path: string[] }

type MySchema = {
    elem: Record<string, Elem>,
    relations: Record<string, Relation>
}

const test: MySchema = {
    elem: {
        elem1: {
            prop1: { name: 'string' },
        },
        elem2: {
            prop2: { name: 'string' },
            prop3: { name: 'string' },
        },
        elem3: {
            prop4: { name: 'string' },
        }
    },
    relations: {
        relation1: {
            path: ['elem2', 'elem1'],
        },
        relation2: {
            path: ['elem3', 'elem1'],
        }
    }
}

I am wondering if it possible to check at compile time if the path array contains values only within the domain of the keys of the elements key at root level.

I cannot understand if with the usage of keyof, generics, or some other Typescript features it is possible to define such relation.

EDIT: Thanks to Shahriar Shojib I got a first valid solution. But I am wondering if it is possible to achieve this without employing a function, but simply specifying the type for the object.



Solution 1:[1]

I am sure there are better ways to achieve this, but this should work for your use case.

type ElemField = { name: string }
type Elem = Record<string, ElemField>

type Relation <T> = { path: T[] }

type MySchema<T extends Record<string, Elem>>  = {
    elem: T,
    relations: Record<string, Relation<keyof T>>
}



function createSchema<T extends Record<string, Elem>>(schema:MySchema<T>) {
    return schema;
}

createSchema({
    elem: {
        elem1: {
            prop1: { name: 'string' },
        },
        elem2: {
            prop2: { name: 'string' },
            prop3: { name: 'string' },
        },
        elem3: {
            prop4: { name: 'string' },
        }
    },
    relations: {
        relation1: {
            path: ['elem2', 'elem1', 'elem5'], // error since it does not exist
        },
        relation2: {
            path: ['elem3', 'elem1'],
        }
    }
})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shahriar Shojib