Chaves de objetos virando tipagem automaticamente <TS>

Vamos construir um problema de tipagem em um software, e usar uma funcionalidade do TS muito legal que automatiza a criação de certas tipagens pra gente.

Vamos direto ao problema: você tem uma lista de usuários, e eles tem cargos diferentes, e acessos diferentes dependendo do cargo.

type TUsuário = {
    nome: string
    cargo: string
    nívelDeAcesso: number
}

const usuáriosDoBanco: TUser[] = [
    {nome: 'joão', cargo: 'administrador', nívelDeAcesso: 1},
    {nome: 'maria', cargo: 'vendedor', nívelDeAcesso: 2},
    {nome: 'rafael', cargo: 'gerente', nívelDeAcesso: 2},
    {nome: 'fernanda', cargo: 'estagiário', nívelDeAcesso: 3},
]

O primeiro problema então é que temos muita informação sobre o tipo de usuário guardada de fato no usuário. Imagina que agora você quer mudar o acesso do usuário gerente, teria que mudar toda sua base de dados... usuário por usuário. péssima idéia. Então vamos melhorar o código.

const tiposDeUsuários = {
    1: {cargo: 'administrador', nívelDeAcesso: 1}
    2: {cargo: 'vendedor', nívelDeAcesso: 2}
    3: {cargo: 'gerente', nívelDeAcesso: 2}
    4: {cargo: 'cliente', nívelDeAcesso: 3}
}

type TUsuário = {
    nome: string
    tipo: number
}

const usuáriosDoBanco: TUsuário[] = [
    {nome: 'joão', tipo: 1},
    {nome: 'maria', tipo: 2},
    {nome: 'rafael', tipo: 3},
    {nome: 'fernanda', tipo: 4},
]

Ok, agora temos uma constante para acessar dados fixos de um tipo de usuários sem precisar ficar guardando tudo no próprio usuário. Mas temos agora um problema com nossa tipagem.

O tipo do usuário está como number, e na realidade, não é qualquer number que pode entrar ali. Veja um exemplo funcionando corretamente

const user = {nome: 'joão', tipo: 1}
const tipoDoUser = user.tipo
const CargoDoUser = tiposDeUsuários[tipoDoUser].cargo 
// O cargo é administrador

Olhe então um exemplo funcionando com erro, onde alguém salvou um tipo que não tinha na sua constante de usuários, simplesmente porque consegue salvar qualquer number.

const user = {nome: 'joão', tipo: 6}
const tipoDoUser = user.tipo
const CargoDoUser = tiposDeUsuários[tipoDoUser].cargo 
// Uncaught TypeError: Cannot read properties of undefined 

Mas e ai, vamos ter que tipar na mão os códigos de nossos usuários?

type TCódigosDeUsuários = 1 | 2 | 3 | 4
// até resolve mas é chato e repetitivo

const tiposDeUsuários = {
    1: {cargo: 'administrador', nívelDeAcesso: 1}
    2: {cargo: 'vendedor', nívelDeAcesso: 2}
    3: {cargo: 'gerente', nívelDeAcesso: 2}
    4: {cargo: 'cliente', nívelDeAcesso: 3}
}

type TUsuário = {
    nome: string
    tipo: TCódigosDeUsuários
}

const usuáriosDoBanco: TUsuário[] = [
    {nome: 'joão', tipo: 1},
    {nome: 'maria', tipo: 2},
    {nome: 'rafael', tipo: 3},
    {nome: 'fernanda', tipo: 4},
]

Até funcionaria, desta forma nosso usuário não aceita mais qualquer número, somente os que definimos, mas temos agora outro problema, dois lugares com a mesma informação, que vamos ter que lembrar de alterar sempre. Achamos então nossa sacada do TS vamos gerar nossa tipagem automática usando as chaves da nossa constante de tipos de usuários

const tiposDeUsuários = {
    1: {cargo: 'administrador', nívelDeAcesso: 1}
    2: {cargo: 'vendedor', nívelDeAcesso: 2}
    3: {cargo: 'gerente', nívelDeAcesso: 2}
    4: {cargo: 'cliente', nívelDeAcesso: 3}
}

type TUsuário = {
    nome: string
    tipo: keyof typeof tiposDeUsuários // a tipagem aqui é : 1 | 2 | 3 | 4
}

const usuáriosDoBanco: TUsuário[] = [
    {nome: 'joão', tipo: 1},
    {nome: 'maria', tipo: 2},
    {nome: 'rafael', tipo: 3},
    {nome: 'fernanda', tipo: 4},
]

As chaves do objeto foram usadas automaticamente para serem as possíveis para aplicar na chave tipo.


Desculpe ter sido muito extenso para uma funcionalidade tão simples, mas gostaria de compartilhar um problema mais passo à passo, para ficar claro o raciocínio.