1.0.0 • Published 5 years ago

copyable v1.0.0

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

Immutable data objects with type safe .copy(), .map() and .equals() inspired by case class in Scala and data class in Kotlin

Installation

yarn add copyable

TypeScript >= 2.8 required.

Usage

import { Copyable } from 'copyable';

class Person extends Copyable {
  constructor(readonly name: string, readonly age: number) {
    super();
  }
}

const person1 = new Person('John', 25);

const person2 = person1.copy({ name: 'James', age: 20 });
// -> Person(name="James", age=20)
person1.copy('age', 30);
// -> Person(name="John", age=30)

person1.map({
  name: name => 'Older ' + name,
  age: age => age + 20
});
// -> Person(name="Older John", age=45)
person1.map('age', age => age + 20);
// -> Person(name="John", age=45)

person1.equals(person2); // false
person1.equals(new Persion('John', 25)); // true

person1.toString();
/* -> 
  Person(
    name="John",
    age=25
  )
*/

Type safety

person1.copy({ age: '20' }); // comple error: Types of property 'age' are incompatible. Type 'string' is not assignable to type 'number'.

person1.copy({ notAPropertyOfPerson: 20 }); // comple error: Object literal may only specify known properties

person1.copy({ equals: () => false }); // Nope. Can't replace methods.

API Reference

copy

  • copy(patch?: Patch<this>): this

    Returns a copy of the object.

    Accepts an object that contains new values for specified properties

    import { Copyable } from 'copyable';
    
    class Person extends Copyable {
      constructor(readonly name: string, readonly age: number) {
        super();
      }
    }
    
    const person1 = new Person('John', 25);
    person1.copy({ name: 'James', age: 20 });
    // -> Person(name="James", age=20)

    Parameters

    • (Optional) patch: Patch<this>

    Returns

    typeof this

  • copy<K extends PatchKey<this>>(key: K, val: this[K]): this

    Returns a copy of the object.

    Accepts a key and a new value for it

    import { Copyable } from 'copyable';
    
    class Book extends Copyable {
      constructor(readonly title: string) {
        super();
      }
    }
    
    const book1 = new Book('TypeScript Deep Dive');
    book1.copy('title', 'TypeScript handbook');
    // -> Book(title="TypeScript handbook")

    Parameters

    • key: K extends PatchKey<this>
    • value: this[key]

    Returns

    typeof this

map

  • map(patchMap: MapPatch<this>): this

    Maps the object. Allows to copy the object based on its current values.

    Accept an object that contains mapper for specified properties.

    const person1 = new Person('John', 25);
    // John got older
    person1.map({
      name: name => 'Older ' + name,
      age: age => age + 20
    });
    // -> Person(name="Older John", age=45)

    Parameters

    • (Optional) patchMap: MapPatch<this>

    Returns

    typeof this

  • map<K extends PatchKey<this>>(key: K, mapper: Mapper<this[K]>): this

    Maps the object. Allows to copy the object based on its current values.

    Accept a key and a mapper for it.

    import { Copyable } from 'copyable';
    
    class Book extends Copyable {
      constructor(readonly title: string) {
        super();
      }
    }
    
    const book1 = new Book('TypeScript Deep Dive');
    book1.map('title', title => title + ' Part 2');
    // -> Book(title="TypeScript Deep Dive Part 2")

Parameters

  • key: K extends PatchKey<this>
  • mapper: Mapper<this[K]>

Returns

typeof this

equals

equals(other: this | null | undefined): boolean

Checks for structural equality

Note that the comparision is shallow!

const book1 = new Book('TypeScript Deep Dive');
const book2 = new Book('TypeScript handbook');

book1.equals(book2); // false
book1.equals(new Book('TypeScript Deep Dive')); // true

Returns

boolean

toString

toString(): string

Returns a string representation of the object that is a bit nicer than the default one ('[object Object]')

const person = new Person('John', 25);
person.toString();
// Person(
//   name="John",
//   age=25
// )

Returns

string

Can I use it with JavaScript?

Yes! You won't have any type checking, of course, so you risk shooting yourself in a foot.

Prior art

Inspired by Case Classes in Scala and Data Classes in Kotlin.

Implementation influenced by ts-copyable and dataclass.