0

Given the following type definitions:

type BaseItem = {
  name: string;
  purchasedAt: string;
  purchasePrice: number;
};

type AvailableItem = BaseItem;

type SoldItem = BaseItem & {
  soldAt: string;
  sellingPrice: number;
};

export type Item = AvailableItem | SoldItem;

Why doesn't TypeScript complain about the following expression?

const invalidItem: Item = {
  name: "foobar",
  purchasedAt: "1-1-2019",
  purchasePrice: 42,
  soldAt: "5-1-2019"
  // `sellingPrice` should be here, or `soldAt` should be absent
};

soldAt and sellingPrice should either both be present, or absent altogether. How would one make TypeScript to enforce this invariant?

Willem-Aart
  • 2,200
  • 2
  • 19
  • 27

1 Answers1

1

I'm not familiar enough with typescript's structural typing system to explain why this occurs, but I don't believe there's any way to make typescript enforce the types as you have them.

The way to get the type safety you want is using discriminated unions (where all types have common constant property eg a kind key ). The following code will error on the invalidItem object in your example.

type AvailableItem = {
    kind: "base";
    name: string; 
    purchasedAt: string;
    purchasePrice: number;
}
type SoldItem = {
    kind: "sold";
    name: string; 
    purchasedAt: string;
    purchasePrice: number;
    soldAt: string;
    sellingPrice: number;
}
export type Item = AvailableItem | SoldItem;

see https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions for more you can do with discriminated unions.

jay_aye_see_kay
  • 522
  • 5
  • 12