Mejora la inferencia de tipos en TypeScript con el operador satisfies

satisfies-operator-typescript

satisfies operator: el operador de TypeScript que no estás usando

Tiempo estimado de lectura: 5 min

  • Valida sin perder inferencia: El operador satisfies valida que una expresión cumpla un tipo sin cambiar el tipo inferido del valor.
  • Mejora la DX en repos grandes: Conserva literales y autocompletado donde las anotaciones tradicionales causan upcast o las aserciones apagan la seguridad.
  • Casos de uso claros: Constants, routes, design tokens y mapeos estáticos se benefician más.
  • Complemento con as const: Úsalo junto a as const para validación más inmutabilidad absoluta.
  • No es para runtime: No reemplaza validación en tiempo de ejecución para datos dinámicos.

Introducción

satisfies operator: el operador de TypeScript que no estás usando es la frase que deberías leer en todos los PRs donde alguien fuerza tipos con as o sacrifica la inferencia de literales. Introducido en TypeScript 4.9, satisfies arregla —sin drama— un problema de tipado que hemos parcheado mal durante años.

Resumen rápido (lectores con prisa)

Qué es: Un operador de TypeScript que valida que un valor cumple un tipo sin cambiar la inferencia del valor.
Cuándo usarlo: Para constantes exportadas, rutas, tokens de diseño y mapeos estáticos donde quieres conservar literales.
Por qué importa: Mantiene autocompletado y seguridad de tipos en grandes bases de código.
Cómo funciona: Verifica el contrato en tiempo de compilación y deja intacta la inferencia literal.

¿Qué hace exactamente el satisfies operator y por qué importa?

El operador satisfies valida que una expresión cumpla con un tipo, pero no cambia el tipo inferido del valor. Es decir: verifica el contrato y deja intacta la inferencia literal del valor. Eso suena pequeño; en equipos con bases de código grandes es una diferencia estructural.

Comparación con anotación y aserción

  • La anotación const x: T = ... valida pero hace upcast: pierde literales.
  • La aserción const x = ... as T fuerza sin validar: apaga la seguridad.
  • const x = ... satisfies T valida y conserva la inferencia.

Ejemplo práctico: tema de colores que no deberías arruinar

Sin satisfies, acabas escribiendo código que obliga al IDE a perder información útil:

type Color = string | [number, number, number];

const theme: Record = {
  primary: "blue",
  secondary: [255, 0, 0]
};

theme.primary.startsWith("b"); // Error: theme.primary es Color, no string literal

Con satisfies:

const theme = {
  primary: "blue",
  secondary: [255, 0, 0]
} satisfies Record;

theme.primary.startsWith("b"); // OK — TypeScript sabe que es string
theme.secondary[0];            // OK — sabe que es number

Validación sin amputación de tipos. Eso es todo.

Casos de uso donde satisfies aporta valor real

– Configuraciones públicas (constants.ts). Valores exportados y consumidos desde varios módulos se benefician de mantener literales.
– Rutas y diccionarios (routing). keyof typeof ROUTES debe devolver claves concretas, no string.
– Design tokens y paletas. Necesitas diferenciar hex strings de tuplas RGB sin perder método de string/array.
– API clients estáticos o mapeos entre endpoints y tipos de respuesta.

Ejemplo de rutas

type RouteConfig = Record;

const ROUTES = {
  home:      { path: "/",    protected: false },
  dashboard: { path: "/app", protected: true  },
} satisfies RouteConfig;

// keyof typeof ROUTES => "home" | "dashboard"

Si hubieras usado : RouteConfig, perderías esas claves y con ellas, seguridad y autocompletado.

Patrón avanzado: satisfies + as const

Úsalos juntos cuando quieras validación + inmutabilidad absoluta:

const ENDPOINTS = {
  users:   "/api/users",
  session: "/api/session",
} satisfies Record as const;

// ENDPOINTS.users es literal "/api/users" y readonly

Esto es ideal para inyectar constantes que se comparten por toda la app sin riesgo de mutación accidental.

Cuándo no usar satisfies

No es una bala de plata. No lo apliques en cada tipo local ni donde el valor no necesite exportarse o conservar literales.

  • Definición y consumo inmediato en el mismo scope → anotación clásica puede ser más clara.
  • Datos dinámicos desde la red → valida en runtime (Zod, io-ts) y transforma antes de confiar en tipos.

Recuerda: satisfies es para diseño estático y claridad, no para validar payloads de clientes externos en producción.

Cómo adoptarlo en un repo sin romper nada

  1. Audit rápido: busca ficheros constants, theme, routes, tokens.
  2. Busca patrones problemáticos: as const seguido de as Type, o const X: Record<...> = {...}.
  3. Reemplaza por satisfies donde quieras conservar literales.
  4. Añade tests de tipo (tsd) para casos críticos y ejecuta tsc --noEmit en CI.
  5. Actualiza guía de estilo y explica el porqué en CONTRIBUTING.md.

Comando grep útil:
grep -R --line-number -E "as const.*as |: Record<|: {[A-Za-z0-9_]+: .*}" src/

Impacto en equipo y mantenimiento

Adoptar satisfies reduce errores sutiles: llamadas a funciones con claves incorrectas, fallos de autocompletado que llevan a as any, y la necesidad de escribir casts defensivos. A nivel de DX, mejora el autocompletado y la intención del código. A nivel de arquitectura, reduce deuda técnica silenciosa: menos as T, menos // @ts-ignore.

Cierre práctico

No es una moda; es una herramienta ergonométrica del tipo system. Si tu repo exporta constantes que consumen otros módulos, haz una pasada hoy mismo y reemplaza las anotaciones que hacen upcast por satisfies. Empieza por constants.ts, theme.ts, routes.ts. Verás menos PRs con as any y más código que documenta intención y comportamiento real.

Implementarlo es simple. Ignorarlo es caro. Esto no acaba aquí: la próxima vez que veas un as en un PR, pregúntate si deberías usar satisfies en su lugar.

Referencia oficial (anuncio): la sección relevante y la documentación general.

FAQ

¿Qué hace exactamente satisfies?

Valida que una expresión cumple con un tipo en tiempo de compilación sin cambiar la inferencia literal del valor.

¿Cuándo debo usarlo en lugar de una anotación de tipo?

Cuando quieras validar un valor pero conservar literales y autocompletado, especialmente en constantes exportadas o mapeos estáticos.

¿Puede reemplazar validación en runtime?

No. Para datos dinámicos desde la red sigue utilizando validadores en runtime (Zod, io-ts) y transforma antes de confiar en tipos estáticos.

¿Cómo se combina con as const?

Úsalos juntos para validación estática más inmutabilidad absoluta; por ejemplo, ... satisfies Record<string,string> as const mantiene literales y readonly.

¿Afecta a keyof typeof?

Sí. Usar satisfies en mapeos permite que keyof typeof infiera claves concretas en lugar de string.

¿Romperá código existente si lo introduzco en un repo grande?

En general no; es una herramienta de seguridad estática. Haz una migración por áreas (constants, theme, routes) y añade pruebas de tipo en CI.

¿Dónde empiezo una auditoría?

Busca ficheros constants, theme, routes, tokens y patrones como as const seguido de as Type o : Record.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *