Skip to content

Instantly share code, notes, and snippets.

@peplocanto
Last active May 3, 2023 12:51
Show Gist options
  • Save peplocanto/39d622228bb2cab23cbb60688cba70b4 to your computer and use it in GitHub Desktop.
Save peplocanto/39d622228bb2cab23cbb60688cba70b4 to your computer and use it in GitHub Desktop.

Type inference & Type check at Runtime

How connect typescript and javascript in our code


As developers, we strive to write robust and error-free code. One of the ways to achieve this is by leveraging TypeScript's powerful type inference system.

This code snippet demonstrates how we can infer TypeScript types from JavaScript variables using the as const syntax. By using the as const syntax, we can tell TypeScript that the variable's values are constant and should not be mutated. This allows TypeScript to infer literal types for the values, making our code more type-safe and reducing the likelihood of runtime errors.

Furthermore, the code snippet also shows how we can use custom type guards to check types at runtime. By defining our own type guards, we can ensure that the values we receive conform to the expected types. This not only helps us catch errors early on but also makes our code more resilient to changes in the data structures.

Overall, leveraging TypeScript's type inference and custom type guards can significantly improve the quality and robustness of our code. By catching errors at compile-time and runtime, we can deliver more reliable software that provides a better experience for our users.

// ============ Data ============
const CANIDAE = ['dog', 'wolf', 'fox', 'coyote'] as const;
const FELINAE = ['cat', 'lion', 'tiger', 'lynx'] as const;
const REPTILIA = ['snake', 'turtle', 'lizard', 'gecko'] as const;
const ANIMALS = {
  canidae: CANIDAE,
  felinae: FELINAE,
  reptilia: REPTILIA,
};

// ============ Inferred Types ============
type Canis = typeof CANIDAE[number];
type Felis = typeof FELINAE[number];
type Reptil = typeof REPTILIA[number];
type Animal = Canis | Felis | Reptil; // typeof ANIMALS[keyof typeof ANIMALS][number];
type Animals = typeof ANIMALS;
type AllowedAnimal = 'dog' | 'cat'; // typeof CANIDAE[0] | typeof FELINAE[0];
type AllowedAnimals = DeepPick<Animals, AllowedAnimal>;

// ============ Type Guards ============
const isCanis = (animal: Animal): animal is Canis =>
  CANIDAE.some((canis) => canis === animal);
const isFelis = (animal: Animal): animal is Felis =>
  FELINAE.some((felis) => felis === animal);
const isReptil = (animal: Animal): animal is Reptil =>
  REPTILIA.some((reptil) => reptil === animal);
const isAllowedAnimal = (animal: Animal): animal is AllowedAnimal =>
  animal === 'dog' || animal === 'cat';

// ============ Functions ============
const getAllowedAnimalsObj = (animals: Animal[]): AllowedAnimals => {
  return animals.reduce((allowedAnimals: AllowedAnimals, animal: Animal) => {
    if (isCanis(animal) && isAllowedAnimal(animal)) {
      allowedAnimals.canidae = [animal];
    }
    if (isFelis(animal) && isAllowedAnimal(animal)) {
      allowedAnimals.felinae = [animal];
    }
    return allowedAnimals;
  }, {} as AllowedAnimals);
};

const getAllowedAnimalsArr = (animals: Animals): AllowedAnimal[] =>
  Object.values(animals)
    .concat()
    .flat(1)
    .filter((animal: Animal): animal is AllowedAnimal => isAllowedAnimal(animal));

// ============ Logs ============
console.log(getAllowedAnimalsObj([...CANIDAE, ...FELINAE, ...REPTILIA]));
console.log(getAllowedAnimalsArr(ANIMALS));

// ============ Utils ============
type DeepPick<
  T extends Record<string, readonly unknown[]>,
  O extends T[keyof T][number]
> = { [P in keyof T]: O extends T[P][number] ? [O] : never };

The as const syntax tells TypeScript that the values of the array or object are immutable (marked as readonly), allowing TypeScript to infer a more specific type. In the code example provided, the as const syntax is used to define three arrays of animals: CANIDAE, FELINAE, and REPTILIA. The typeof operator is then used to define three types, Canis, Felis, and Reptil, which correspond to the individual elements of each array. ¡note the use of [number]!

Type guards are functions that allow to determine whether a variable is of a certain type at runtime. In this example, the isCanis, isFelis, isReptil, and isAllowedAnimal functions are all type guards that check whether a given animal is of a specific type at runtime.

Additional resources

Play with it on https://www.typescriptlang.org/play

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment