Q:

Exhaustiveness checks without explicit strings

Assigning to (or asserting) never at the end of a function is a technique used in Typescript in order to force exhaustive checks at compile time.
For the compiler to detect this, however, it requires explicit strings to check against for determining if the function definitively returns before the assignment/assertion of never.
Would it be possible to introduce some sort of typed variation of Object.freeze that only works on object literals, and further up the chain, so that something like the following could be done? 
Even better, is there a way to create an interface wherein the keys are automatically those of each of Action.type (in this example)? If that were the case - actionMap could simply be declared as that interface, which would force the check at compiletime.
Both are solutions to the same problem... given only a discriminated union, is it possible to do exhaustiveness checks like this, at compiletime, without needing to use explicit strings in the function?
interface Increment {
    type: 'increment'
}

interface Decrement {
    type: 'decrement'
}

type Action = Increment | Decrement

const inc: Increment = { type: 'increment' };
const dec: Decrement = { type: 'decrement' };

//this would be a typescript variation
const actionMap = Object.freeze({
    [inc.type]: n => n + 1,
    [dec.type]: n => n-1
});

function doAction(action: Action, val: number): number {

    if(actionMap[action.type]) {
        return actionMap[action.type](val);
    }

    //this would error at compile time if the above checked failed 
    const _exhaustiveCheck: never = action;
}

console.log(doAction(inc, 1));
console.log(doAction(dec, 1));

A:

There is a fairly straight forward way to make a map that guarantees that it has a value for each case in a discriminated union. You simply have to set it so that the type of its keys is the discriminated union identifier. 
type ActionMap = {
    [P in Action["type"]]: (val:number)=>number
}; 

You can then implement this interface which will look something like this:
var map: ActionMap = {
   decrement: n => n - 1,
   increment: n=> n + 1
}

Edit: After a bunch of messing around I found a much more versatile and powerful solution that lets you not only type the keys of the discriminated union values but also allow you to type the payload. 
First: Define your union in the form of key:type pairs. (I think this is cleaner to read anyway)
type Actions = {
    "increment": { incrementValue: number }
    "decrement": { decrementValue: number }
}

Second: Create an Action Discriminated Union from that map. This isn't the clearest code in the world, what it does is for each key value pair in ActionsMap create a new type by adding a type value {type:key} then sum all those types together to create your Discriminated Union. 
type Action = {
    [P in keyof Actions]: { type: P } & ActionsMap[P]
}[keyof Actions];

Third:Create a type for your map
type ActionsMap = {
    [P in  keyof Actions]: (val:number,action:Actions[P])=>number
}

Forth: Enjoy your entirely type safe action/reducer map!
const map:ActionsMap = {
    decrement: (val, action) => val + action.decrementValue,
    increment: (val, action) => val + action.incrementValue,
}

Fair warning. This very much pushes the limits on what the typescript definition can do and I personally have been bitten by relying on some of typescripts fringe behavior only to have it be changed in the next version. 

