cis
cis

Reputation: 1379

Intersection vs union in mapped type

Following up on my question Force object properties to pick one interface key, I have now a problem when using the return value of one function as an argument to another function - though the types should be the same within the same key.

Consider this code:

interface ItemTypePerModule {
    module1: { foo1: string }
    module2: { foo2: string }
}

type ModuleMapping<T> = {
    [P in keyof T]: {
        read: () => Promise<T[P]>
        update: (existingItem: T[P]) => Promise<void>
    }
}

const moduleReadAndUpdate: ModuleMapping<ItemTypePerModule> = {
    module1: {
        read: () => { return Promise.resolve( {foo1: "myItem"} ) },
        update: (existingItem) => {
            console.log(existingItem.foo1)
            return Promise.resolve()
        },
    },
    module2: {
        read: () => { return Promise.resolve( {foo2: "myItem"} ) },
        update: (existingItem) => {
            console.log(existingItem.foo2)
            return Promise.resolve()
        },
    }
}

async function doStuffInAllModules() {
    const modules = Object.keys(moduleReadAndUpdate) as Array<keyof ItemTypePerModule>
    for(const mod of modules) {
        const item = await moduleReadAndUpdate[mod].read()
        await moduleReadAndUpdate[mod].update(item)
    }
}

In the last code line (await moduleReadAndUpdate[mod].update(item)) I get the error

Argument of type '{ foo1: string; } | { foo2: string; }' is not assignable to parameter of type '{ foo1: string; } & { foo2: string; }'.
  Type '{ foo1: string; }' is not assignable to type '{ foo1: string; } & { foo2: string; }'.
    Property 'foo2' is missing in type '{ foo1: string; }' but required in type '{ foo2: string; }'.

Actually, within the same module the return value of read and the param for update should be typed the same. I.e. when I'm processing module1 then the type is always { foo1: string } and never { foo2: string }. However, according to the error message it seems TypeScript doesn't really "understand" and does some weird assumptions with unions and intersections instead.

So, I'm inclined to think that this might be a TypeScript limitation. But how can I work around it?

Upvotes: 0

Views: 202

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250206

You can't correlate mod and item in this way, typescript just doesn't understand this pattern. You will need to use a type assertion:

async function doStuffInAllModules() {
    const modules = Object.keys(moduleReadAndUpdate) as Array<keyof ItemTypePerModule>
    for(const mod of modules) {
        const item = await moduleReadAndUpdate[mod].read()
        await moduleReadAndUpdate[mod].update(item as never)
    }
}

Playground Link

Upvotes: 1

Related Questions