JSPM

  • Created
  • Published
  • Downloads 21227
  • Score
    100M100P100Q162966F
  • License Apache-2.0

A tiny library that brings Tiny Types to JavaScript and TypeScript

Package Exports

  • tiny-types

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (tiny-types) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Tiny Types

npm version Build Status Coverage Status Commitizen friendly npm Known Vulnerabilities

TinyTypes is an npm module that makes it easy for TypeScript and JavaScript projects to give domain meaning to primitive types. It also helps to avoid all sorts of bugs and makes your code easier to refactor. Learn more.

Installation

To install the module from npm:

npm install --save tiny-types

Defining Tiny Types

An int on its own is just a scalar with no meaning. With an object, even a small one, you are giving both the compiler and the programmer additional information about what the value is and why it is being used.

Jeff Bay, Object Calisthenics

Single-value types

To define a single-value TinyType - extend from TinyTypeOf<T>():

import { TinyTypeOf } from 'tiny-types';

class FirstName extends TinyTypeOf<string>() {}
class LastName  extends TinyTypeOf<string>() {}
class Age       extends TinyTypeOf<number>() {}

Every tiny type defined this way has a readonly property value of type T, which you can use to access the wrapped primitive value. For example:

const firstName = new FirstName('Jan');

firstName.value === 'Jan';

Equals

Each tiny type object has an equals method, which you can use to compare it by value:

const 
    name1 = new FirstName('Jan'),
    name2 = new FirstName('Jan');

name1.equals(name2) === true; 

ToString

An additional feature of tiny types is a built-in toString() method:

const name = new FirstName('Jan');

name.toString() === 'FirstName(value=Jan)';

Which you can override if you want to:

class Timestamp extends TinyTypeOf<Date>() {
    toString() {
        return `Timestamp(value=${this.value.toISOString()})`;
    }
}

const timestamp = new Timestamp(new Date());

timestampt.toString() === 'Timestamp(value=2018-03-12T00:30:00.000Z))'

Multi-value and complex types

If the tiny type you want to model has more than one value, or you want to perform additional operations in the constructor, extend from TinyType directly:

import { TinyType } from 'tiny-types';

class Person extends TinyType {
    constructor(public readonly firstName: FirstName,
                public readonly lastName: LastName,
    ) {
        super();
    }
}

You can also mix and match both of the above definition styles:

import { TinyType, TinyTypeOf } from 'tiny-types';

class UserName extends TinyTypeOf<string>() {}

class Timestamp extends TinyTypeOf<Date>() {
    toString() {
        return `Timestamp(value=${this.value.toISOString()})`;
    }
}

abstract class DomainEvent extends TinyTypeOf<Timestamp>() {}

class AccountCreated extends DomainEvent {
    constructor(public readonly username: UserName, timestamp: Timestamp) {
        super(timestamp);
    }
}

const event = new AccountCreated(new UserName('jan-molak'), new Timestamp(new Date()));

Even such complex types still have both the equals and toString methods:

const 
    now = new Date(2018, 2, 12, 0, 30),
    event1 = new AccountCreated(new UserName('jan-molak'), new Timestamp(now)),
    event2 = new AccountCreated(new UserName('jan-molak'), new Timestamp(now));
    
event1.equals(event2) === true;

event1.toString() === 'AccountCreated(username=UserName(value=jan-molak), value=Timestamp(value=2018-03-12T00:30:00.000Z))'

Serialisation to JSON

Every TinyType defines a toJSON() method, which returns a JSON representation of the object. This means that you can use TinyTypes as Data Transfer Objects.

Single-value TinyTypes are serialised to the value itself:

import { TinyTypeOf } from 'tiny-types';

class FirstName extends TinyTypeOf<string>() {}

const firstName = new FirstName('Jan');

firstName.toJSON() === 'Jan'

Complex TinyTypes are serialised recursively:

import { TinyType, TinyTypeOf } from 'tiny-types';

class FirstName extends TinyTypeOf<string>() {}
class LastName extends TinyTypeOf<string>() {}
class Age extends TinyTypeOf<number>() {}
class Person extends TinyType {
    constructor(
        public readonly firstName: FirstName,
        public readonly lastName: LastName,
        public readonly age: Age,
    ) {
        super();
    }
}

const person = new Person(new FirstName('Bruce'), new LastName('Smith'), new Age(55));

person.toJSON() === { firstName: 'Bruce', lastName: 'Smith', age: 55 }

De-serialisation from JSON

Although you could define standalone de-serialisers, I like to define them as static factory methods on the TinyTypes themselves:

import { TinyTypeOf } from 'tiny-types';

class FirstName extends TinyTypeOf<string>() {
    static fromJSON = (v: string) => new FirstName(v);
}

const firstName = new FirstName('Jan'),

FirstName.fromJSON(firstName.toJSON()).equals(firstName) === true

Optionally, you could take this further and define the shape of serialised objects to ensure that fromJSON and toJSON are compatible:

import { TinyTypeOf } from 'tiny-types';

type SerialisedFirstName = string;
class FirstName extends TinyTypeOf<string>() {
    static fromJSON = (v: SerialisedFirstName) => new FirstName(v);
    toJSON(): SerialisedFirstName {
        return super.toJSON() as SerialisedFirstName;
    }
}

const firstName = new FirstName('Jan'),

FirstName.fromJSON(firstName.toJSON()).equals(firstName) === true

This way de-serialising a more complex TinyType becomes trivial:

import { JSONObject, TinyType } from 'tiny-types';

interface SerialisedPerson extends JSONObject {
    firstName:  SerialisedFirstName;
    lastName:   SerialisedLastName;
    age:        SerialisedAge;
}

class Person extends TinyType {
    static fromJSON = (v: SerialisedPerson) => new Person(
        FirstName.fromJSON(v.firstName),
        LastName.fromJSON(v.lastName),
        Age.fromJSON(v.age),
    )

    constructor(public readonly firstName: FirstName,
                public readonly lastName: LastName,
                public readonly age: Age,
    ) {
        super();
    }

    toJSON(): SerialisedPerson {
        return super.toJSON() as SerialisedPerson;
    }
}

Your feedback matters!

Do you find TinyTypes useful? Give it a star!

Found a bug? Need a feature? Raise an issue or submit a pull request.

Have feedback? Let me know on twitter: @JanMolak

License

TinyTypes library is licensed under the Apache-2.0 license.


- Copyright © 2018- Jan Molak