3
votes

Introduction to problem

I am struggling to develop strong typing for a particular function written in TypeScript...

For illustrative purposes, lets say I work at the Department of Motor Vehicles and have a function registerVehicles that can accept registration details for many vehicles organised by name, and returns the license plates for each vehicle.

interface RegistrationDetails {
    transmission?: 'manual' | 'automatic';
    wheelCount?: number;
    horsePower?: number;
}

const licensePlates = registerVehicles({
    sportsCar: {
        transmission: 'manual',
        wheelCount: 4,
        horsePower: 762
    },
    motorBike: {
        transmission: 'manual',
        wheelCount: 2
    },
    hoverBoard: {
        // All registration details are optional
    }
});

The function returns an object with the names of each vehicle and their newly registered license plates:

expect(licensePlates).to.eql({
    sportsCar: 'ABCD-1234',
    motorBike: 'SPDY-5678',
    hoverBoard: 'BTTF-2'
});

The function exists and works perfectly, the problem is getting it strongly typed.

The solution must meet the following criteria:

  1. The variable licensePlates should be implicitly typed from the result of the function.

  2. Trying to pass a registration detail that doesn't exist should error at compile time.

    registerVehicles({
        car: {
            cowPower: 500 // <-- Spelling mistake, should be horsePower
        }
    })
    
  3. Trying to access the license plate of a vehicle you didn't register should error at compile time:

    const licensePlates = registerVehicles({
        ferrari: {
            horsePower: 562
        }
    });
    
    alert(licensePlates.furrari); // <-- Spelling mistake, should be ferrari
    
  4. TypeScript should know each license plate is a string at compile-time

    const licensePlates = registerVehicles({
        ferrari: {}
    });
    
    alert(licensePlates.ferrari * 5); // <-- Error, you can't multiple a string
    

I've gotten close, but every solution I try ultimately fails to meet at least one of the above requirements. Help me Stack Overflow community, you're my only hope!

1

1 Answers

3
votes

The utility types "Record" does what you want. With it you can map dynamic properties from one type to another type. (Try it in the Playground):

function registerVehicles<K extends string>(p: Record<K, RegistrationDetails> ):  Record<K, string> { 
    return null;
}

The K type will be a String Literal Types eg. "sportsCar" | "motorBike" | "hoverBoard".

Update: It is not well documented. But here is a link to the Documentation and I found a example here.