mrt

Helps build expressive chained interfaces.

Usage no npm install needed!

<script type="module">
  import mrt from 'https://cdn.skypack.dev/mrt';
</script>

README

npm version license type Build Status Coverage Status Code Climate bitHound Score bitHound Dependencies npm downloads Source: ECMAScript 6

Overview

MrT is a tool for making simple and complex chained interfaces on javascript libraries.

The resulting syntax is easy-to-read while still being flexible and 100% vanilla-js.

// Example interface for a web server

const webServer = new WebServer()

.public
  .get("/account")
    .action(accountController.list)
  .get("/account/:id")
    .action(accountController.show)

.authenticated
  .authorized("owner", "admin")
    .put("/account/:id")
      .action(accountController.update)
  .authorized("admin")
    .post("/account")
      .action(accountController.create)
    .delete("/account/:id")
      .action(accountController.delete)

.listen(3000);

Installation

The easiest and fastest way to install MrT is through the node package manager:

$ npm install mrt --save

API Guide

.initialize(...constructorArguments)

Whenever one constructor extends another, .super must be called in the extended object's constructor before this can be called.

This can be a gotcha for many developers, so MrT has a built-in pseudo-constructor called .initialize that can be used by extended objects without the need to call .super.

import Component from "mrt";

// Without using initialize
class Person extends Component {
  constructor(name) {
    super(name);
    this.properties("name");
    this.name(name);
  }
}

// Using initialize
class Person extends Component {
  initialize(name) {
    this.properties("name");
    this.name(name);
  }
}

.properties(...propertyNames)

Define a simple chainable property that sets and gets a raw value:

class Person extends Component {
  initialize() {
    this.properties("name");
  }
}

const person = new Person();

person.name("Jana");
person.name(); // "Jana"

.multi

class Person extends Component {
  initialize() {
    this.properties("nicknames").multi;
  }
}

const person = new Person();

person.nicknames("Jana Banana", "Jananana");
person.nicknames(); // ["Jana Banana", "Jananana"]

.aggregate

class Person extends Component {
  initialize() {
    this.properties("nicknames").aggregate;
  }
}

const person = new Person();

person.nicknames("Jana Banana");
person.nicknames("Jananana");
person.nicknames(); // ["Jana Banana", "Jananana"]

.multi.aggregate

class Person extends Component {
  initialize() {
    this.properties("citiesVisited").multi.aggregate;
  }
}

const person = new Person();

person.citiesVisited("Toledo", "OH");
person.citiesVisited("Colorado Springs", "CO");
person.citiesVisited(); // [["Toledo", "OH"], ["Colorado Springs", "CO"]]

.multi.aggregate.flat

class Person extends Component {
  initialize() {
    this.properties("values").multi.aggregate.flat;
  }
}

const person = new Person();

person.values("one", "two");
person.values("three", "four");
person.values(); // [ "one", "two", "three", "four" ]

.merged

class Person extends Component {
  initialize() {
    this.properties("favoriteCities").merged;
  }
}

const person = new Person();

person.mergedValues({"CO": "Colorado Springs", "KS": "Wichita"});
person.mergedValues({"CO": "Boulder"});
person.mergedValues(); // {"CO": "Boulder", "KS": "Wichita"}

.filter(filterFunction)

class Person extends Component {
  initialize() {
    this.properties("favoriteNumber").filter(this.castIntegers);
  }

  castIntegers(value) {
    const newValue = parseInt(value);
    if (newValue) {
      return newValue;
    } else {
      return value;
    }
  }
}

const person = new Person();

person.favoriteNumber("1");
person.favoriteNumber(); // 1

.then(callback)

Call a synchronous callback each time a new value is set on the property

class Person extends Component {
  initialize() {
    this.properties("favoriteNumber").then(this.doSomething);
  }

  doSomething(value) {
    // No returns. Just a simple synchronous callback.
  }
}

const person = new Person();

person.favoriteNumber("1");
person.favoriteNumber(); // 1

.link(linkName, linkConstructor)

A link creates a method which returns a new instance of the provided linkConstructor.

The new instance is given a copy of all methods from the parent link. This is what enables MrT to chain multiple tiers.

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.link("wheel", Wheel);
  }
}

class Wheel extends Component {
  initialize(diameter) {
    this.properties("diameter");
    this.diameter(diameter);
  }
}

const car = new Car();

const wheel1 = car.wheel(10);

car
.wheel(10)
.wheel(10)
.wheel(10);

.getter

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.link("wheel", Wheel).getter;
  }
}

class Wheel extends Component {}

const car = new Car();

const wheel1 = car.wheel;

car
.wheel
.wheel
.wheel;

.inherit(...propertyNames)

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.properties("color");
    this.link("door", Door).inherit("color");
  }
}

class Door extends Component {}

const car = new Car();

car.color("Red");

const door = car.door;

door.color(); // "Red"

.into(collectionName)

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.properties("color");
    this.link("door", Door).into("doors");
  }
}

class Door extends Component {}

const car = new Car();

car
.door
.door;

car.doors.length; // 2

.into(collectionName).key(keyName)

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.properties("color");
    this.link("door", Door).into("doors").key("side");
  }
}

class Door extends Component {
  initialize(side) {
    this.properties("side");
    this.side(side);
  }
}

const car = new Car();

car
.door("left")
.door("right");

car.doors.left; // left door object
car.doors.right; // right door object

.then(thenFunction)

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.properties("color");
    this.link("door", Door).then(newDoor => {
      // Called each time a link is created
    });
  }
}

class Door extends Component {}

const car = new Car();

car
.door()
.door();

.apply(...passedArguments)

import Component from "mrt";

class Car extends Component {
  initialize() {
    this.properties("color");
    this.link("door", Door).apply(this);
  }
}

class Door extends Component {
  initialize(car, color) {
    car; // Automatically passed by .arguments(this) in Car
        color; // "green"
  }
}

const car = new Car();

car.door("green");