Skip to content

MethodChain

James edited this page Jun 24, 2017 · 3 revisions

method builders

declaration

// https://github.com/iluwatar/java-design-patterns/tree/master/step-builder
// https://github.com/iluwatar/java-design-patterns/tree/master/builder
class MethodChain extends Chain {
  // --- these 3 are used in every other method (almost) ---

  // defaults to `this.set(key, value)`
  public onSet(fn: Fn): MethodChain
  // defaults to .onSet ^
  public onCall(fn: Fn): MethodChain
  // defaults to `this.get(key)`
  public onGet(fn: Fn): MethodChain

  // --- types ---

  // type validation
  // @example `?string`, `string[]`, `string|boolean`, `boolean[]|string[]`
  public type(type: string | FnHasSingleArg): MethodChain

  // an object that contains nestable types
  // they are mapped to validators
  public schema(schema: Obj): ChainAble

  // when using .encase or .type, defaults to re-throw
  // called when type validation | encased method is invalid
  public onInvalid(fn: Fn): MethodChain
  public catch(fn: Fn): MethodChain // alias

  // called when type validation | encased method isn't invalid
  public onValid(fn: Fn): MethodChain
  public then(fn: Fn): MethodChain // alias

  // --- decorators/factories - they decorate/build the method ---

  // wraps the method in a try catch, responds to
  public encase(method?: string, rethrow?: boolean): MethodChain

  // binds the method to thisArg, or to parent with no params
  public bind(thisArg?: Obj | boolean): MethodChain

  // wraps the method to return `parent` by default
  public returns(value: any): MethodChain
  // public chainable(): MethodChain // alias ^

  // will make the method call the value in .returns
  public callReturns(should?: boolean): MethodChain

  // aliases an array of methods
  // @example .name('eh).alias('canada')
  //          obj.eh = .onCall
  //
  //          obj.canada = obj.eh
  //           ^ is > Object.define(obj, canada, Object.getDescriptor(obj.eh))
  public alias(methods: strings): MethodChain

  // defaultParamValue
  // @example .default('canada') becomes...
  //           .eh(arg = 'canada' => onCall(arg))
  public default(value?: any): MethodChain

  // sets the value right away
  // @example .name('eh').initial(true)
  //          obj.store: Map<eh, true>
  //          obj.eh = .onCall
  public initial(value?: any): MethodChain

  // defineGetterSetter
  public define(should?: boolean): MethodChain

  // expandNameToSetMethodGetMethod
  // @example .name('eh') decorates an object...
  //           obj.setEh = .onSet
  //          obj.getEh = .onGet
  //           obj.eh    = .onCall
  public getSet(should?: boolean): MethodChain

  // --- important operations ---

  // finish the method building, naming is from the builder pattern,
  // returns the `returnValue` or `this.parent`
  public build(returnValue: Primitive): Primitive
  public build(returnValue?: null | undefined | any): Chain
  // calls .build using Symbol.toPrimative with `+`
  // @example +chain.method(name)
  public toNumber(): number

  // decorate an object, useful when using nested factories
  // this previously was .decorateParent
  public decorate(target: Obj): MethodChain

  // this is called from Chain.method(name) / Chain.methods(names)
  public names(names: strings | Obj): MethodChain
  public name(names: strings | Obj): MethodChain // ^ alias

  // --- simple .factory presets ---

  // is a factory that adds a .onCall & .initial
  // every time the method is called, it auto-increments
  // @example .name('index').autoIncrement()
  //          .index()   // now index is 1
  //          .index(+1) // now index is 2, note the optional arg for clarity
  public autoIncrement(should?: boolean): MethodChain

  // @example .name('created_at')
  //          obj.createdAt = .onCall
  public camelCase(should?: boolean): MethodChain

  // add custom factories that are called **for each .name**
  // used mainly for when building multiple .names
  // so some properties are reset, or retained
  // the above 2 methods use this
  public factory(fn: MethodChainFactory): MethodChain
}

api

all examples start with this, or constructor() when extending

const chain = new Chain()

❗ using + will call .build() in a shorthand fashion

onSet

defaults to this.set(key, value)

public onSet(fn: Fn): MethodChain

onCall

defaults to .onSet ^

public onCall(fn: Fn): MethodChain

onGet

defaults to this.get(key)

public onGet(fn: Fn): MethodChain

schema

an object that contains nestable .types they are recursively (using an optimized traversal cache) mapped to validators

❗ this method auto-calls .build, all other method config calls should be done before it 📝 TODO: link to deps/is docs

public schema(schema: Obj): ChainAble
chain
  .methods()
  .define()
  .getSet()
  .onInvalid((error, arg, instance) => console.log(error))
  .schema({
    id: '?number',
    users: '?object|array',
    topic: '?string[]',
    roles: '?array',
    creator: {
      name: 'string',
      email: 'email',
      id: 'uuid',
    },
    created_at: 'date',
    updated_at: 'date|date[]',
    summary: 'string',
  })

// valid
chain.created_at = new Date()
chain.setCreatedAt(new Date())

isDate(chain.created_at) === true

// nestable validation 👍
chain.merge({creator: {name: 'string'}})

// invalid
chain.updated_at = false

type

public type(type: string | FnHasSingleArg): MethodChain

runtime type validation

.type(`?string`)
.type(`string[]`)
.type(`string|boolean`)
.type(`boolean[]|string[]`)
.type(`!date`)

encase

wrap a method in try catch, with config for .catch & .then (aka .onInvalid, .onValid)

onInvalid

@alias then

when using .encase or .type, defaults to re-throw called when type validation | encased method are invalid

export interface onInvalid {
  (error: TypeError, arg: any, chainable: ChainAble)
  call?: onInvalid
}

onValid

@alias catch

interface onValid {
  (arg: any, chainable: ChainAble)
  call?: onValid
  // this = ChainInstance
}

called when type validation | encased method is not inValid (throws or returns false)

alias

+chain.methods(['canada']).alias(['eh'])

chain.eh('actually...canada o.o')
isTrue(chain.get('canada') === 'actually...canada o.o')

define

makes the method a getter/setter with .onGet & .onSet used respectively commonly used with .getSet for an api with the best of both worlds

+chain.method('ehOh').define()

// this is === by default, but any decorations with any of the other functions
// would make it not strictly identical
chain.set('ehOh', true) === chain.ehOh = true
chain.get('ehOh')       === chain.ehOh

getSet

+chain.method('ehOh').getSet()

// camelCases the names out for clearly communicating set & get
chain.setEhOh(true)
chain.getEhOh() === true

autoIncrement

is a factory that adds a .onCall & .initial every time the method is called, it auto-increments

chain.method(['index']).index().index(+1).index(+1)
chain.get('index') === 3

default

❗ this is the default parameter value

// this is essentially
chain.lies = function lies(arg = false) {
  return this.set('lies', arg)
}
chain.methods(['truth']).default(true).build().truth()
isTrue(chain.get('truth') === true)

chain.methods(['lies']).default(false).build().lies()
isFalse(chain.get('lies'))
+chain.method(['thing1', 'thing2']).default('dr')

const {thing1, thing2} = chain.thing1().thing2().entries()
isTrue(thing1 === 'dr')
isTrue(thing1 === thing2)

initial

sets the value right away

+chain.name('eh').initial(true)
obj.store.get('eh') === true

bind

binds methods to the instance if an argument is passed into .bind(theArgIsTheBindTarget)

const {eq} = require('chain-able')

chain.bindMe = function() {
  isTrue(eq(chain, this))
}
+chain.methods(['bindMe']).bind()
chain.bindMe()

returns

@alias chainable

iterates over methods, wraps them so their value returns Chain

class Eh extends Chain {
  constructor(parent) {
    super(parent)
    +this.method('canada').returns(this)

  }
  canada(arg) {
    console.log(arg)
  }
}

const eh = new Eh()
eh.canada('log me') // this now returns Eh

callReturns

will make the method call the value in .returns

+eh.method('dude').returns(() => 'dude').callReturns()
eh.dude() === 'dude'

camelCase

camelCases method name using factory

+chain.method('eh_oh').camelCase()

chain.ehOh(true)

build

builds the method, returns parent or argument

factory

used as middleware/plugins/factories for building each method

decorate

random example

+chain
  .onInvalid((e, arg, instance) => console.error(e))
  .onValid((arg, instance) => console.log('valid'))
  .type('?string[]')
  .returns(chain)

🔗 related

🔗 related