1.1.0 • Published 5 years ago

valueobject.ts v1.1.0

Weekly downloads
1
License
MIT
Repository
github
Last release
5 years ago

valueobject.ts


Tiny typesafe value object library for TypeScript.

npm.io npm.io CircleCI codecov

Features

  • ecmascript 5
  • about 1k bytes with zero dependencies
  • commonjs & es module
  • typesafe and immutable class properties
  • object keys filtering in runtime
  • defining static type and runtime object keys at once

Brief example

import {valueObject, type, ValueType} from "valueobject.ts";

class Person extends valueObject({
    name: type.string,
    age: type.number,
}) {
    greet(): string {
        return `Hello, I am ${this.name}.`;
    }
    growOne(): Person {
        return new Person({...this, age: this.age + 1});
    }
}

const initialValue = {
    name: "Bob",
    age: 20,
    greet: null,
    growOne: () => {
        throw new Error("The method won't be overwritten!");
    },
};
const person = new Person(initialValue as ValueType<typeof Person>);

console.log(person.greet());
// "Hello, I am Bob."
console.log(person.growOne().age);
// 21

Why and when to use this?

In TypeScript, you can easily create value object with parameter properties like the following:

class Person {
    constructor(public readonly name: string, public readonly age: number) {}
    greet(): string {
        return `Hello, I am ${this.name}.`;
    }
    growOne(): Person {
        return new Person(this.name, this.age + 1);
    }
}

However, with more properties, you'll want to use named parameters like the following:

class SomeLargeValueObject {
    public readonly prop1: number | null;
    public readonly prop2: number | null;
    public readonly prop3: number | null;
    /**
     * ... more props ...
     */
    constructor(args: {
        prop1: number | null;
        prop2: number | null;
        prop3: number | null;
        /**
         * ... more props ...
         */
    }) {
        this.prop1 = arg.prop1;
        this.prop2 = arg.prop2;
        this.prop3 = arg.prop3;
        /**
         * ... more assingments ...
         */
    }
}

With many properties, this approach is frustrating. So many prior value object libraries introduce the following approach.

interface ValueObjectConstructor<T extends {[k: string]: any}> {
    new (initialValue: T): Readonly<T>;
}

const valueObject = <T extends {[k: string]: any}>(): ValueObjectConstructor<T> => {
    return class {
        constructor(arg: T) {
            Object.assign(this, arg);
        }
    } as any;
};

//-----------------

interface SomeLargeValueData {
    prop1: number | null;
    prop2: number | null;
    prop3: number | null;
    /**
     * ... more props ...
     */
}

class SomeLargeValueObject extends valueObject<SomeLargeValueData>() {
    isValid(): boolean {
        /** ... */
    }
}

In TypeScript, however, this approach has a problem. Because TypeScript has no exact type, a runtime error occurs in the following case.

const factory = (data: SomeLargeValueData): SomeLargeValueObject => {
    return new SomeLargeValueObject(data);
}

const passedData = {
    prop1: 1,
    prop2: 2,
    prop3: 3,
    /**
     * ... more props ...
     */
    // Oops! This will overwrite the class method!
    isValid: true,
    /**
     * ... some more other props for other usecases ...
     */
};

const nextValueObject = factory(passedData);

//-----------------

// TypeError: isValid is not a function
if (nextValueObject.isValid()) {
    /** ... */
}

Because of that, this library filters constructor argument's property keys.

Installation

Please use npm.

$ npm install valueobject.ts

Then, use javascript module bundler like webpack or rollup to bundle this library with your code.

API reference

#function valueObject(typedef)

Returns value object base class. The base class has 2 method, toJSON() which returns plain object of its data, and equals(other) which compare shallowly with passed parameter other. The parameter typedef's type is plain object with properties type TypeHolder, which can create with type<T>() function.

#function type<T>()

Returns TypeHolder that contains type T, to create value object type definition. Its basic usage is like following.

class Comment extends valueObject({
    createdAt: type<Date>(),
    text: type<string>(),
    parent: type<Comment | null>(),
}) {
    /** ... */
}

type() has aliases of following frequent usecases.

  • type.string
    • equals to type<string>()
  • type.number
    • equals to type<number>()
  • type.boolean
    • equals to type<boolean>()
  • type.null
    • equals to type<null>()
  • type.undefined
    • equals to type<undefined>()
  • type.array<T>(arg?: T)
    • example: type.array(type.string)
  • type.optional<T>(arg?: T)
    • required to define optional field
  • type.union<T>(...args: T[])
    • example: type.union(type.string, type.null)

#type ValueType<T>

Returns value object data type of the type parameter. Please use like following.

type PersonData = ValueType<Person>; // or `ValueType<typeof Person>;`

Credits

1.1.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

0.0.0-alpha4

5 years ago

0.0.0-alpha3

5 years ago

0.0.0-alpha2

5 years ago

0.0.0-alpha1

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago