Simple Value Types in Typescript
Tags: typescript, oop, ddd
Published: 2023-03-09 15:00:00


_Value Types_, or _Value Objects_, are core components of OOP design inheriting principles from Domain Driven Design, like entities and agregates. More often than not, entities have an identity: a unique domain identifier. They usually have a `id` field. Examples includes Users, Invoices, etc. Their content only doesn't differenciate enought between two entities.

On the other hand, _Value Types_, don't have a specific identity, but are identified by their content. It's not _who_ they are, but _what's their content_. Like and Address, or an Email Address. They can introduce more semantic to your code, increase the safety, maintainability and testability. All this comes at the expanse of some degree of complexity. Especially in hydration and serialization scenarios.

For a lot of _Value Types_, they encapsulate a primitive value, wrapped and protected by domain specifications. Some are specific to a domain, and other cover more general use cases. In my opinion, introducing more complexity for domain _Value Types_ is alright, but it's not for the generic ones. It's especially frustrating to have complex _Value Type_, when no behaviour is attached to theme.

How would you impement a **simple**, yet generic _Value Type_ in Typescript?

---

Let's dive into an example, from a previous article, where we created a [Window Iterable](https://dev.to/jquagliatini/prevent-prisma-from-reaching-bind-parameters-limit-3448#the-full-code).

```ts
class AtLeastOneWindowIterable<T> implements Iterable<T[] | undefined> {
  protected constructor(
    private readonly values: readonly T[],
    private readonly size: number
  ) {}

  static of<U>(values: readonly U[]) {
    return {
      by: (size: number) => new AtLeastOneWindowIterable(
        values,
        size,
      ),
    };
  }
}
```

What if we increased the security by introducing a _Value Type_ that only allow _positive integers_ for the window size.

Let's start with the type:

```ts
type StrictlyPositiveInt = number & { __name: 'StrictlyPositiveInt' };
```
Adding the `& { __name: string }` is common practice to create "Branded Types".
It prevents TS from keeping the structural equivalence between a `number` and `StrictlyPostiveInt`. If that the case, we can't simply assign a `number`. The following won't work, and that's exactly our intention:

```ts
const a: StrictlyPositiveInt = 3;
  //  〰️ Type 'number' is not assignable to type 'StrictlyPositiveInt'.
```

We need a constructor:

```ts
function StrictlyPositiveInt(v: any): StrictlyPositiveInt {
  const n = Number(v);

  if (!Number.isFinite(n)) throw new Error(`${v} is not a number`);
  if (Math.round(n) !== n) throw new Error(`${n} is not an integer`);
  if (n <= 0) throw new Error(`${n} must be positive`);

  return n as StrictlyPositiveInt;
}
```

If you ever used [zod](https://zod.dev), that's equivalent to

```ts
import { z } from 'zod';

const StrictlyPositiveInt = z.number().int().min(1).brand('StrictlyPositiveInt');
type StrictlyPostiveIntT = z.infer<typeof StrictlyPositiveInt>;
```

I encourage you to read their page on [branded types](https://zod.dev/?id=brand).

Using it, becomes quite easy:

```ts
class AtLeastOneWindowIterable<T> implements Iterable<T[] | undefined> {
  protected constructor(
    private readonly values: readonly T[],
    private readonly size: StrictlyPositiveInt
  ) {}

  static of<U>(values: readonly U[]) {
    return {
      by: (size: number) => new this(
        values,
        StrictlyPositiveInt(size),
      ),
    };
  }
}
```

Notice here, that the `Iterable` encapsulates the usage of the _Value Type_. In my opinion there are few reasons to expose internal rules to the outside world. From experience, having entities responsible for creating their _Value Types_ is quite maintainable, even for more complex scenarios.

The only added complexity here comes from the exceptions. How you handle it will depend on your use cases. But usually, I just don't and prefer to let it bubble up.

Now, let's assume for a minute, that we need to send this `StrictlyPositiveInt` over the wire. No fuss or worries. We simply stringify it.

```ts
JSON.stringify(StrinctlyPositiveInt(32_000))
```

Since, our _Value Type_, is only some type decoration, and runtime validation, there is no complexity. And since, our `Iterable` takes a `number` as input, the hydration is also painless. Win - Win!

---

_Branded Types_, _Value Types_, _Value Objects_. There are a ton of names for a simple logic encapsulation. Do you use it? How do you name it? Let me know.