TypeScript – Define type alias as subtype of another type alias?

In TypeScript, is it possible for a type alias to "implement" (or become subtype of) another type alias?

For example, I have the following type aliases (TS Playground Link):

type Predicate<TValue> = {
    op: "EQUAL" | "NOT_EQUAL",
    value: TValue,
}

type Predicates = Record<string, Predicate<string> | Predicate<boolean>>

/**
 * An implementation of the {@link Predicates} type.
 */
type AnimalPredicates = {
    species: Predicate<string>,
    isMammal: Predicate<boolean>,
}

// EXAMPLE:
const animalPredicates: AnimalPredicates = {
    species: { op: "EQUAL", value: "shiba_inu" },
    isMammal: { op: "EQUAL", value: true },
}

// `AnimalPredicates` is a subtype of `Predicates` so this is allowed
const animalPredicates2: Predicates = animalPredicates

The AnimalPredicates type alias is an implementation of the Predicates type (i.e. AnimalPredicates is subtype of Predicates).

Although I added a JSDoc comment saying that AnimalPredicates implements Predicates, it does not prevent me from doing something like:

type AnimalPredicates = {
  // Predicate<number> is NOT allowed as record value in `Predicates`
  // But TS compiler does not complain...
  species: Predicate<number>,  
}

Is there a workaround to let the TS compiler know that AnimalPredicates must be a subtype of the Predicate type?

>Solution :

You can make AnimalPredicates extend Predicates


type Predicate<TValue> = {
    op: "EQUAL" | "NOT_EQUAL",
    value: TValue,
}

type Predicates = Record<string, Predicate<string> | Predicate<boolean>>

/**
 * An implementation of the {@link Predicates} type.
 */
interface AnimalPredicates extends Predicates  {
    species: Predicate<string>,
    isMammal: Predicate<boolean>,
}

// EXAMPLE:
const animalPredicates: AnimalPredicates = {
    species: { op: "EQUAL", value: "shiba_inu" },
    isMammal: { op: "EQUAL", value: true },
}

// `AnimalPredicates` is a subtype of `Predicates` so this is allowed
const animalPredicates2: Predicates = animalPredicates

see TS playground

you can also clean it a bit by restricting the generic itself

type Predicate<TValue extends string | boolean> = {
    op: "EQUAL" | "NOT_EQUAL",
    value: TValue,
}
/**
 * An implementation of the {@link Predicates} type.
 */
interface AnimalPredicates   {
    species: Predicate<string>,
    isMammal: Predicate<boolean>,
}

// EXAMPLE:
const animalPredicates: AnimalPredicates = {
    species: { op: "EQUAL", value: "shiba_inu" },
    isMammal: { op: "EQUAL", value: true },
}

see TS playground

Leave a Reply