As interfaces datilografadas podem expressar restrições de co-ocorrência para propriedades

Aug 17 2020

Existe um padrão dentro de uma Interface Typescript monolítica ou definições de tipo para afirmar que as propriedades aparecem juntas ou não aparecem?

Por exemplo, um item pode ser válido se for assim ...

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

...ou isto...

{
  id:"ljklkj",
}

No entanto, seria inválido se qualquer uma das propriedades da verificação ortográfica ocorresse isoladamente.

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

Monolítico

É claro que o caso simples acima pode ser resolvido criando um Data e um Tipo ou Interface SpellcheckData. No meu caso de aplicação, entretanto, haverá mais de um 'cluster' de propriedades coocorrentes. Definir um novo tipo para cada combinação de coocorrência levaria a uma explosão de tipos para expressar o caso.

Por esse motivo, me referi à solução como uma interface "monolítica". Claro, pode ser necessário usar alguma forma de composição para defini-lo.

O que eu tentei

Tentei encontrar exemplos como este na referência da linguagem Typescript, mas sem saber como o recurso pode ser chamado (ou se é um recurso que pode ser expresso), estou lutando. As propriedades podem ser individualmente opcionais, mas não consigo ver uma maneira de expressar a coocorrência.

Tecnologias Relacionadas

Um recurso equivalente para validação de dados XML é discutido aqui ... https://www.w3.org/wiki/Co-occurrence_constraints

Para JSON, entendo que as linguagens de esquema como Schematron e Json Content Rules são capazes de expressar co-restrições.

Exemplo trabalhado

Se eu fosse imaginar a sintaxe de texto digitado para o caso de co-restrição aplicada a conjuntos de parâmetros HTTP para o mecanismo de pesquisa Solr, poderia parecer assim, indicando que você pode optar por satisfazer totalmente os parâmetros Spell ou Group, ou não - uma união em que cada tipo é opcional (indicado por ? ) ...

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

Isso contrasta com o exemplo abaixo, que acredito ser o Typecript correto, mas requer TODOS os 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

Respostas

3 Lesiak Aug 17 2020 at 15:45

Por favor, tente o seguinte. Parece que mostra erros nos lugares corretos.

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: ""};

Link do parque

Atualizar

Se você preferir passar tipos como uma tupla, você pode usar o seguinte utilitário:

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]>

Se algumas propriedades forem necessárias:

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