StrictUnion, который позволяет дополнительные свойства - PullRequest
1 голос
/ 10 января 2020

Учитывая это StrictUnion generi c:

type UnionKeys<T> = T extends T? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends T? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>> : never;
export type StrictUnion<T> = StrictUnionHelper<T, T>

type Working = StrictUnion<(
  { id: number } | 
  { uuid: string } | 
  { memberId: number } | 
  { memberUuid: string } | 
  { member: Member } | 
  { organizationId: number, contactId: number })
>

const a: Working = { id: 1 }
const b: Working = { uuid: '123456' }
const c: Working = { organizationId: 1, contactId: 1 }
const d: Working = { organizationId: 1 }
const e: Working = { id: 1, organizationId: 1, contactId: 1 } // I would like this to work
const f: Working = { }

Я бы хотел StrictUnion, чтобы разрешить "дополнительные" свойства. Это означает, что это должно работать:

{ id: 1, organizationId: 1, contactId: 1 }

Я ищу совпадения, чтобы соответствовать хотя бы одному из союзов, но также допускаю дополнительные свойства, то есть вы не сможете просто пропустить organizationId потому что ему нужно contactId, но вы сможете пройти только id, но не empty {}.

Проблема, если вы используете регулярное объединение, заключается в следующем: если есть способ использовать регулярное объединение и решить эти проблемы, я бы принял это как решение.

enter image description here

  async member(props: (
    { id: number } | 
    { uuid: string } | 
    { memberId: number } | 
    { memberUuid: string } | 
    { member: Member } | 
    { organizationId: number, contactId: number })
  ) {
    const member = await (async () => { 
      if (props.member) return member
      if (props.id) return this.memberRepository.readOneByIdOrFail(props.id)
      if (props.memberId) return this.memberRepository.readOneByIdOrFail(props.memberId)
      if (props.uuid) return this.memberRepository.readOneByUuidOrFail(props.uuid)
      if (props.memberUuid) return this.memberRepository.readOneByUuidOrFail(props.memberUuid)
      if (props.organizationId && props.contactId) return this.memberService.readByUnique(props)
      throw new Error(`unable to ${MemberService.name}.member`)
    })()
    return { member }
  }

Больше мыслей:

type Requirements = (
  { id: number } | 
  { uuid: string } | 
  { memberId: number } | 
  { memberUuid: string } | 
  { member: Member } | 
  { organizationId: number, contactId: number }
)

✅ Should be valid
❌ Should not be valid

const a: Requirements = { id: 1 } ✅
const b: Requirements = { uuid: '123456' } ✅
const c: Requirements = { id: null, uuid: null } ✅ (will throw, but valid)
const d: Requirements = { uuid: '123456', contactId: 1 } ✅
const e: Requirements = { organizationId: 1, contactId: 1 } ✅
const f: Requirements = { uuid: '12345', organizationId: 1, contactId: 1 } ✅
const g: Requirements = { uuid: null, organizationId: 1, contactId: 1 } ✅
const h: Requirements = { organizationId: 1 } ❌
const i: Requirements = { id: 1, organizationId: undefined } ✅
const j: Requirements = { } ❌

Мне нравится то, что делает AtLeastOne:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]

Что мешает пустому объекту {}

AtLeastOne<{
    id: number,
    uuid: string,
    memberId: number,
    memberUuid: string,
    member: Member,
    organizationId: number,
    contactId: number
}>

Я просто хочу, чтобы organizationId или contactId были недействительными в одиночку, потому что они - пара.

Вот некоторые идеи имен для того, что я хочу PartialPairs или AtLeastOnePair:

PartialPairs<
  { id: number } | 
  { uuid: string } | 
  { memberId: number } | 
  { memberUuid: string } | 
  { member: Member } | 
  { organizationId: number, contactId: number }
>

AtLeastOne<{
  { id: number } | 
  { uuid: string } | 
  { memberId: number } | 
  { memberUuid: string } | 
  { member: Member } | 
  { organizationId: number, contactId: number }
}>
...