Patrones
¿Cómo acepto un objeto de opciones donde todos los campos son opcionales pero el objeto vacío no vale?
Construye una variante por clave donde esa clave es obligatoria y el resto opcionales, y únelas. Cualquier clave suministrada satisface al menos una variante.
La receta
type AtLeastOne<T> = { [K in keyof T]: Required<Pick<T, K>> & Partial<Omit<T, K>> }[keyof T] type Filters = { name?: string age?: number } type ValidFilters = AtLeastOne<Filters>
La compilación compila esto y comprueba cada resultado de abajo.
Cómo funciona
- 01
Required<Pick<T, K>> & Partial<Omit<T, K>>
Una variante: esta clave obligatoria, todo lo demás opcional. `Required` importa porque las propiedades de origen ya eran opcionales.
- 02
}[keyof T]Indexa toda la tabla por todas las claves a la vez, colapsando las variantes en una unión. El mismo truco con el que el archivo convierte una tabla de consulta en una respuesta.
Lo que obtienes
{ name: 'a' } extends ValidFilters ? true : false→true{} extends ValidFilters ? true : false→falseEl objeto vacío no satisface ninguna variante, porque cada variante exige exactamente una clave.
{ name: 'a'; age: 1 } extends ValidFilters ? true : false→true
Dónde falla
La unión crece con el número de claves, y los mensajes de error con ella — un tipo de opciones de diez claves produce una unión de diez miembros y un diagnóstico que nadie leerá. Resérvalo para bolsas de opciones pequeñas.
Conclusión
Mapea a variantes y luego indexa por `keyof` para unirlas. Ese doble paso es la forma general de «al menos uno de estos».