Advanced TypeScript's capabilities in real-life scenario
Tags: typescript
Published: 2019-05-06 16:19:47

---
title: Advanced TypeScript's capabilities in real-life scenario
published: true
description: Modelling statically-typed HTTP weather data API client, using types composing, conditional types and types inferring
tags: typescript
---
## Brief

In this article I present how I built statically-typed, dynamic TypeScript client for HTTP weather data API. This is a real-life case I encountered during my work.

The article focuses on TypeScript's capabilities of types composing and inferring. I describe how these capabilities can be utilized to define statically-typed, yet dynamically-composed types model for weather data metrics.

### Code sample

I've created this [StackBlitz](https://stackblitz.com/edit/typescript-gzcx6q) with all code mentioned in the article wired up, so you can see how the pattern works and experiment with it by yourself.

## Introduction

I'm recently working on remote weather stations system. Each station's task is to constantly collect weather data. Metrics are sent to a server and stored in a database and so on... But that's a whole different story right now.

Let's focus on weather data.

Single weather metric consists of values and is defined as follows:

```
timestamp: Date;
temperature: number;
pressure: number;
humidity: number;
rain: number;
sun: number;
windDirection: number;
windVelocity: number;
dew: number;
```

During work I encountered interesting problem. I needed to read metrics with either few or all values selected, preferably retrieved in single database roundtrip. Therefore I've built HTTP API endpoint allowing me to do exactly that. The endpoint gave me great dose of flexibility which I really liked but I was having difficulty in achieving the same amount of neatness in consumer.

Skipping some details, to retrieve station's metrics, server expects as parameter bit flags `MetricValue` indicating which values consumer wants to receive. Server then returns selected values along with corresponding timestamps packed as JSON array. This approach allows me to reuse the same metrics endpoint without need to implement specific use cases separately.

`MetricValue` bit flags definition in TypeScript looks like this:

```typescript
enum MetricValue {
  Temperature = 1 << 0,
  Pressure = 1 << 1,
  Humidity = 1 << 2,
  Rain = 1 << 3,
  Sun = 1 << 4,
  WindDirection = 1 << 5,
  WindVelocity = 1 << 6,
  Dew = 1 << 7
}
```

Example requests:

```
> GET metrics/1
// 1 === MetricValue.Temperature
[{timestamp: "00:01", temperature: 20.2}, {timestamp: "00:02", temperature: 20.3}, ...]

> GET metrics/3
// 3 === MetricValue.Temperature | MetricValue.Pressure
[{timestamp: "00:01", temperature: 20.2, pressure: 1013}, {timestamp: "00:02", temperature: 20.3, pressure: 1013.1}, ...]
```

Cool, isn't it? But how can we type this creature?

## Defining API client's requirements

To be fair, my first idea, which would probably Just Work&trade;, was type single `MetricDto` like follows:

```typescript
interface MetricDto {
  readonly timestamp: string;
  readonly temperature?: number;
  readonly pressure?: number;
  // other optional values ...
}
```

Like I said, this would be probably fine but it wasn't completely satisfying due to fact of losing compile-time information of what values I am exactly retrieving. All these nullable values are just awful and can lead to errors or far too many `undefined` checks.

Oh, and I spent recently too much time reading about fancy TypeScript's type system features to just let it go.

I decided to come up with something more strict and composable and I'll show you everything I discovered.

## Breaking contract declaration into components

First of all, I decided to break my MetricDto into separate interfaces. One for each value.

```typescript
interface TimestampDto {
  readonly timestamp: string;
}

interface TemperatureDto {
  readonly temperature: number;
}

interface PressureDto {
  readonly pressure: number;
}

// ...
```

Note I declared specific properties as non-optional as opposed to original `MetricDto` declaration. This allows me to reuse above interfaces in different scenarios without redeclaring API contracts.

With original `MetricDto` declaration broken down to single-value interfaces, we can type `MetricDto` as:

```typescript
type MetricDto =
    TimestampDto
  & Partial<TemperatureDto>
  & Partial<PressureDto>
  & Partial<HumidityDto>
  & Partial<RainDto>
  & Partial<SunDto>
  & Partial<WindDirectionDto>
  & Partial<WindVelocityDto>
  & Partial<DewDto>;
```

I used here predefined type called `Partial<T>` which causes all properties of generic argument `T` to be marked as optional.

By the way, we can write custom mapped types like `Partial<T>`, but we don't need any in this case.

In result, we get model of `MetricDto` which is equivalent to initial one. The difference is, the new one is far more composable.

Ok, that's cool, you may say, but it gives us basically nothing new. We rewrote the same code with some fancy stuff, but we didn't get any real benefits, except for some misty promise of composability.

You're right and it's time to push things little bit further.

## Mapping MetricValue flag to its corresponding DTO type

While wondering what else can I do, I found out that I can use enum values as types. The condition to do so is to declare only constant enum values, so I had to change `MetricValue` declaration to contain precalculated flags values.

```typescript
enum MetricValue {
  Temperature = 1,
  Pressure = 2,
  Humidity = 8,
  Rain = 16,
  Sun = 32,
  WindDirection = 64,
  WindVelocity = 128,
  Dew = 256
}
```

Maybe it's not the most convenient but it's more than fair trade-off for what we'll achieve soon. As I mentioned, this change lets us to use enum values as types. So, we can do now things like:

```typescript
type Temperature = MetricValue.Temperature;
```

I discovered that I can use this feature along with conditional types to map `MetricValue` with its corresponding `*Dto` interface.

```typescript
type Dto<T extends MetricValue> =
    T extends MetricValue.Temperature ? TemperatureDto
  : T extends MetricValue.Pressure ? PressureDto
  : T extends MetricValue.Humidity ? HumidityDto
  : T extends MetricValue.Rain ? RainDto
  : T extends MetricValue.Sun ? SunDto
  : T extends MetricValue.WindDirection ? WindDirectionDto
  : T extends MetricValue.WindVelocity ? WindVelocityDto
  : T extends MetricValue.Dew ? DewDto
  : never;
```

Example usage:

```typescript
type Result = Dto<MetricValue.Temperature>;

// Result is TemperatureDto.
```

Hmm, that's interesting but it's still not too dynamic. What we really want is a way to find out at compile-time which `MetricValue`s consumer is querying and thus what will be exact returned DTO type.

## Magic kicks in - inferring DTO type from generic tuple of MetricValue types

After some time of getting familiar and struggling with type inference using `infer` keyword, I finally came up with the following solution:

```typescript
type UnionDto<T extends MetricValue[]> = T extends (infer U)[] ? (U extends MetricValue ? Dto<U> : never) : never;
```

What this mapping does is taking tuple of `MetricValue` types and then creating from the tuppled types a union of mapped `*Dto` types. It's best seen on example:

```typescript
type Result = UnionDto<[MetricValue.Temperature, MetricValue.Pressure]>;

// Result is TemperatureDto | PressureDto.
```

Now we are getting somewhere. Union is not something that fullfills our requirements, though. Intersection is what we need. Luckily, that's not a problem because we can convert union to intersection in no time.

```typescript
type UnionToIntersection<T> = (T extends any ? (x: T) => void : never) extends ((x: infer U) => void) ? U : never;
```

We can now create type properly resolving `MetricValue`s to composed `Dto`.

```typescript
type CompositeDto<T extends MetricValue[]> = UnionToIntersection<UnionDto<T>>;
```

Which gives us ability to do exactly what we wanted - infer resulting `Dto` from `MetricValue`s passed as arguments.

```typescript
type Result = CompositeDto<[MetricValue.Temperature, MetricValue.Pressure]>;

// Result is TemperatureDto & PressureDto.
```

## Wiring things up

We finally have everything we need to model type we're after all the time. Let's look at it.

```typescript
type Metric<T extends MetricValue[]> = TimestampDto & CompositeDto<T>;
```

`Metric<T>` is type representing metric fetched from API which consists of mandatory timestamp and intersects additional `Dto`s representing specific metric's values which are inferred from passed `MetricValue`s.

```typescript
type Result = Metric<[MetricValue.Temperature, MetricValue.Pressure]>;

// Result is TimestampDto & TemperatureDto & PressureDto.
```

That's lot of theory but how we can actually use it in practice?

## Building API client

Finally, we can assemble the API client. I'll obscure implementation details to keep things simple, but crucial is function resembling this one:

```typescript
function fetchMetrics<T extends MetricValue[]>(stationId: number, ...metrics: T): Promise<Metric<T>[]> {
  // Details of request...
}
```

```typescript
// 'metrics' is array of TimestampDto & TemperatureDto & PressureDto.
fetchMetrics(stationId, MetricValue.Temperature, MetricValue.Pressure).then(metrics =>
  // IDE hints and static typing goodness available without writing any explicit types!
  for (const metric of metrics) {
    console.log(metric.temperature, metric.pressure);
  }
});
```

Neat!

Our `fetchMetrics` function is statically-typed and its returned DTO type is dynamically composed, based on what metric values we are requesting.

We built powerful client which we can now reuse in many use cases without needing to handle whole bunch of nullable properties or manually writing narrowed types for specific use case.

If that's not cool then I don't know what is.

## Summary

Since the very beginning of my TypeScript adventure, I've heard about its rich typing features. I must admit that until this very moment I wasn't aware the typing features were in fact `that` rich.

The pattern surely has more applications than just that. Please, share if you discover more clever stuff like this.

Hope you enjoyed my ideas. I'm keen on discussing any matters related to the subject.