How can I preserve the "as const" constraint in Typescript when an object (with such a constraint) is used to build another

Viewed 97

Here is the code with some comments about what I would like to do and where I have an issue (more details about the issue after the code block):

type AttributeDefinition = {
    name:string

}
type EntityDefinition = {
    name: string
    attributes: Readonly<AttributeDefinition[]>
}

type EntityDefinitionSource = Partial<EntityDefinition>

type Entity<ET extends EntityDefinition> = {
    [K in ET['attributes'][number] as K['name']]: any
}

/////////////////////////////////////////

// this function returns the given object, and is only used to constraint the object type
// this is what I had previously, but now that I want to add missing properties on returned object I have an issue - look at makeDefinitionFromSource()
function makeDefinition<D extends EntityDefinition>(definition:D): D{
    return definition
}

const def1 = makeDefinition({
    name: 'myEntityDefinition',
    attributes: [
        {name:'foo'}
    ] as const
})

const entity1a: Entity<typeof def1> = {
    foo: 42, // here typescript accept the "foo" key, which is the expected behavior
}

const entity1b: Entity<typeof def1> = {
    baz: 42, //here typescript refuse to use "baz" as a key, which is the expected behavior
}

/////////////////////////////////////////

// this function return a new object using the given source object, but the "as const" constraint from source is not preserved on returned object
function makeDefinitionFromSource<D extends EntityDefinitionSource>(definition:D): EntityDefinition{
    return {
        name: definition.name || 'defaultName',
        attributes: definition.attributes
        //attributes: definition.attributes as const
        //                                    ↳ one attempt was to add the constraint here, but it throws an error
        // TS1355: A 'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals
    }
}

const def2 = makeDefinitionFromSource({
    attributes: [
        {name:'foo'}
    ] as const
})

const entity2a: Entity<typeof def2> = {
    foo: 42, // here typescript accept the "foo" key, which is the expected behavior (but it does because of a lack of typing)
}

const entity2b: Entity<typeof def2> = {
    baz: 42, // so here typescript does not reject he "baz" key, which is not the expected behavior (it accept any key)
}

As mentioned in the comments, I would like to use an object (an EntityDefinitionSource) to build another one (an EntityDefinition) using a function that would set default values to missing properties. Is there a way to preserve such a constraint, or to add it (I would even prefer this approach) when the returned object is built (look at my commented attempt in makeDefinitionFromSource)?

0 Answers
Related