composable-factory v1.0.8
Composable factory
Define and use composable factory functions with fun!
Being heavily inspired by stampit, this library promotes Composable Factory Functions (CFF)
as an alternative pattern to use over Class Hierarchies
and let you combine multiple reusable objects together.
Why should I use this library?
ComposableFactory
focuses on simplicity and provides a clean API without thousands of options and possibilities which create only confusion. (Keep it simple, stupid).You wanted a composable factory function - you got it! Nothing more!
This library has a small footprint of about 48 SLOC.
Advanced TypeScript support. Types definitions not only cover
ComposabeFactory
built-in methods but also the properties and methods of the created object instances. Demo:
import { ComposableFactory } from 'composable-factory';
interface TodoInterface {
name: string,
status: number,
setDone?: () => void,
someMethod?: () => string
}
ComposableFactory<TodoInterface>({
properties: {
// Only name, status are valid properties.
// TypeScript will complain if you put something else
},
init({ name }) {
// this.name, this.status, this.setDone() and this.someMethod() are all accessible
// 'this' scope is TodoInterface
},
methods: {
// Only methods from TodoInterface are acceptable.
// Methods signatures are checked while you are implementing them
someMethod() {
// this.name, this.status, this.setDone() and this.someMethod() are all accessible
// 'this' scope is TodoInterface
}
},
});
// typescript will complain about missing options
const TodoFactory = ComposableFactory<TodoInterface>();
// typescript will complain about missing options.properties given that name and status are required
const TodoFactory = ComposableFactory<TodoInterface>({});
// typescript will complain again about missing status property
const TodoFactory = ComposableFactory<TodoInterface>({
properties: {
name: '',
}
});
// OK. Typescript will ignore missing options.methods because both setDone() and someMethod() are optional.
const TodoFactory = ComposableFactory<TodoInterface>({
properties: {
name: '',
status: 0,
}
});
- Finally if you ever want to understand how this library works, the source code is friendly readable and clean.
Table of content
Installation
npm install composable-factory --save
Usage
The API for both JavaScript and TypeScript is almost the same. JavaScript users should ignore types parameters and remove angle brackets (<>). For this reason the examples in this section and also in the API docs will be demonstrated using TypeScript.
TypeScript
import { ComposableFactory } from 'composable-factory';
JavaScript
const { ComposableFactory } = require('composable-factory');
Usage example
interface EngineInterface {
speed: number,
accelerate: (incr: number) => number,
decelerate: (decr: number) => number,
}
interface BreaksInterface {
speed: number,
stop(): number
}
interface BodyInterface {
colour: string,
design: string,
}
const EngineFactory = ComposableFactory<EngineInterface>({
// default properties
properties: {
speed: 0,
maxSpeed: 180,
},
init({ maxSpeed }) {
if (maxSpeed) this.maxSpeed = maxSpeed;
},
methods: {
accelerate(incr) {
this.speed = this.speed + incr;
return this.speed;
},
decelerate(decr) {
this.speed = this.speed - decr;
return this.speed;
}
}
});
const BreaksFactory = ComposableFactory<BreaksInterface>({
methods: {
stop() {
while (this.speed > 0) {
this.speed = this.speed - 1;
}
return 0;
}
}
});
const BodyFactory = ComposableFactory<BodyInterface>({
// default properties
properties: {
colour: 'yellow',
design: 'suv'
},
init({ colour, design }) {
if (colour) this.colour = colour;
if (design) this.design = design;
}
});
const Car = EngineFactory.compose(BreaksFactory).compose(BodyFactory);
const car = Car({design: 'sport car', maxSpeed: 300});
expect(car.speed).toEqual(0)
expect(car.maxSpeed).toEqual(300)
expect(car.color).toEqual('yellow')
expect(car.design).toEqual('sport car')
car.accelerate(100);
expect(car.speed).toEqual(100)
car.stop();
expect(car.speed).toEqual(0)
API
A picture is worth a thousand words. The big picture looks like:
type ComposableFactoryParams = {
properties: {[key:string]: any},
init(initOptions: {[key:string]:any}) => void,
methods: { (...args: any[]) : any },
statics: { [key: string]: any },
}
ComposableFactory(ComposableFactoryParams) => ComposableInterface
type initOptions = {
[key: string]: any
};
ComposableInterface(initOptions) => Object instance
ComposableInterface.compose( ComposableFactoryParams | ComposableInterface ) => ComposableInterface
ComposableFactory.compose(ComposableInterface[]) => ComposableInterface
See API Reference for more details.
Contributing
So you are interested in contributing to this project? Please see CONTRIBUTING.md.