¿Pueden las interfaces de TypeScript expresar restricciones de co-ocurrencia para propiedades?

Aug 17 2020

¿Existe un patrón estándar dentro de una interfaz de Typecript monolítica o definiciones de tipo para afirmar que las propiedades aparecen juntas o no aparecen en absoluto?

Por ejemplo, un artículo podría ser válido si tuviera este aspecto ...

{
  id:"ljklkj",
  spellcheck:true,
  spellcheckModel:"byzantine",
}

...o esto...

{
  id:"ljklkj",
}

Sin embargo, no sería válido si cualquiera de las propiedades del corrector ortográfico se produjera de forma aislada.

{
  id:"ljklkj",
  spellcheckModel:"byzantine",
}
{
  id:"ljklkj",
  spellcheck:true,
}

Monolítico

Por supuesto, el simple caso anterior podría resolverse creando un tipo de datos y un tipo o interfaz de revisión ortográfica. En mi caso de aplicación, sin embargo, habrá más de un 'grupo' de propiedades concurrentes. Definir un nuevo tipo para cada combinación de co-ocurrencia conduciría a una explosión de tipos para poder expresar el caso.

Por esta razón, me he referido a la solución como una interfaz "monolítica". Por supuesto, puede ser necesario utilizar alguna forma de composición para definirlo.

Lo que he intentado

He intentado encontrar ejemplos como este dentro de la referencia del lenguaje Typecript, pero sin saber cómo se podría llamar la función (o si es una función que se puede expresar), estoy luchando. Las propiedades pueden ser individualmente opcionales, pero no veo una forma de expresar la co-ocurrencia.

Tecnologías relacionadas

Aquí se analiza una característica equivalente para la validación de datos XML ... https://www.w3.org/wiki/Co-occurrence_constraints

Para JSON, entiendo que los lenguajes de esquema como Schematron y Json Content Rules pueden expresar co-restricciones.

Ejemplo resuelto

Si tuviera que imaginar la sintaxis mecanografiada para el caso de co-restricción aplicada a los conjuntos de parámetros HTTP para el motor de búsqueda Solr, podría tener este aspecto, lo que indica que puede optar por satisfacer completamente los parámetros de Hechizo o Grupo, o no en absoluto - una unión en la que cada tipo es opcional (indicado por el ? ) ...

type SolrPassthru =
  SolrCoreParams & (
    SolrSpellParams? |
    SolrGroupParams?  
  )

Esto contrasta con el ejemplo siguiente, que creo que es Typecript correcto, pero requiere TODOS los parámetros de cada grupo de parámetros.

type SolrCoreParams = {
  defType: SolrDefType,
  boost: SolrBoostType,
}

type SolrSpellParams = {
  spellcheck: "true" | "false",
  "spellcheck.collate": "true" | "false",
  "spellcheck.maxCollationTries": 1,
}

type SolrGroupParams = {
  group: "true" | "false",
  "group.limit": '4'
  "group.sort": 'group_level asc,score desc,published desc,text_sort asc'
  "group.main": 'true'
  "group.field": 'group_uri'
}

type SolrPassthru =
  SolrCoreParams & 
  SolrSpellParams &
  SolrGroupParams

Respuestas

3 Lesiak Aug 17 2020 at 15:45

Por favor intenta lo siguiente. Parece que muestra errores en los lugares correctos.

type None<T> = {[K in keyof T]?: never}
type EitherOrBoth<T1, T2> = T1 & None<T2> | T2 & None<T1> | T1 & T2

interface Data {
  id: string;
}

interface SpellCheckData {
  spellcheck: boolean,
  spellcheckModel: string,
}

// Two interfaces
var z1: EitherOrBoth<Data, SpellCheckData> = { id: "" };
var z2: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true,  spellcheckModel: 'm'};
var z3ERROR: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true};
var z4: EitherOrBoth<Data, SpellCheckData> = { id: "", spellcheck: true,  spellcheckModel: 'm'};

interface MoreData {
  p1: string,
  p2: string,
  p3: string,
}

type Monolith = EitherOrBoth<Data, EitherOrBoth<SpellCheckData, MoreData>>

var x1: Monolith  = { id: "" };
var x2: Monolith  = { spellcheck: true,  spellcheckModel: 'm'};
var x3ERROR: Monolith  = { spellcheck: true};                       
var x4: Monolith  = { id: "", spellcheck: true,  spellcheckModel: 'm'};
var x5ERROR: Monolith  = { p1: ""};                                  
var x6ERROR: Monolith  = { p1: "", p2: ""};
var x7: Monolith  = { p1: "", p2: "", p3: ""};
var x8: Monolith  = { id: "", p1: "", p2: "", p3: ""};
var x9ERROR: Monolith  = { id: "", spellcheck: true, p1: "", p2: "", p3: ""};
var x10: Monolith  = { id: "", spellcheck: true, spellcheckModel: 'm', p1: "", p2: "", p3: ""};

Enlace de juegos

Actualizar

Si prefiere pasar tipos como una tupla, puede utilizar la siguiente utilidad:

type CombinationOf<T> = T extends [infer U1, infer U2] ? EitherOrBoth<U1, U2> :
                        T extends [infer U1, infer U2, infer U3] ? EitherOrBoth<U1, EitherOrBoth<U2, U3>> :
                        T extends [infer U1, infer U2, infer U3, infer U4] ? EitherOrBoth<U1, EitherOrBoth<U2, EitherOrBoth<U3, U4>>> :
                        never;

type Monolith = CombinationOf<[Data, SpellCheckData, MoreData]>

Si se requieren algunas propiedades:

type Monolith = Data & CombinationOf<[Data, SpellCheckData, MoreData]>