4.0.7 • Published 8 years ago

brisky-base v4.0.7

Weekly downloads
3
License
ISC
Repository
github
Last release
8 years ago

brisky-base

Extendable object constructors, build for speed, low memory consumption and simplicity

  • set method
  • easy extendable property defintions
  • deep, memory efficient prototypes
  • types (simple inheritance)
  • inject pattern
  • traversal helpers

Build Status js-standard-style npm version Coverage Status


###Manipulation

set

Set method, set values or objects on a base object, always does a deep merge on objects

const base = require('brisky-base')

const foo = base({
  a: {},
  b: {}
}) // → Base { a: {}, b: {} }

foo.set({
  a: {
    c: true
  }
}) // → Base { a: { c: true }, b: {} }

Base objects allow fields to be an object and a primitive at the same time

const base = require('brisky-base')
const foo = base({
  a: true,
  b: true // internaly primitives are stored on the field .val
}) // → Base { a: true, b: true }

foo.set({
  a: {
    c: true
  }
}) // → Base { a: { val: true, c: true }, b: true }

remove

Remove a base object

const base = require('brisky-base')
const b = base({ foo: true, bar: true, })
b.foo.remove() // removes foo and all nested properties
b.set({ bar: null }) // same as b.bar.remove()
b.set(null) // removes b and all nested properties

reset

Overwrite base, removes all properties that end up in .keys()

const base = require('brisky-base')
const foo = base({
  a: true,
  b: true,
  properties: { c: true },
  c: 'haha non-key property' // c is not a part of .keys()
})

foo.set({ reset: true, x: true }) // removes a and b, but not c, adds x
// reset can also be used as a method foo.reset()

move

Move a property to another property

const base = require('brisky-base')
const foo = base({ a: 100, b: true, }) // → Base { a: 100, b: true }
foo.move('a', 'b') // → Base { b: 100 }
foo.move('b', 'c') // → Base { c: 100 }

inject

Like a set but only called once, used for composition of modules and behaviour.

When injecting an module or object (injectable) several times, it will only execute the first time. This is useful when working in a highly modularized structure where multiple modules might inject the same injectable.

const base = require('brisky-base')
const foo = base()

const someModule = require('someModule')
// somemodule is eg. { a: 'hello' }

const otherModule = require('someModule')
// otherModule is eg. { b: 'bye' }

foo.inject(someModule, otherModule) // calls a set for both modules → Base { a: 'hello', b: 'bye' }
foo.set({
  inject: [ someModule, otherModule ] // set notation
})

define

Shortcut for Object.defineProperty Wraps define-configurable

const base = require('brisky-base')
const foo = base({
  key: 'base'
  define: {
    x: true, // defines { value: true }
    bla (val) {} // defines a method "bla"
  }
})

foo.define({
  extend: {
    bla (method, val) { // extends "bla" method
      return method.call(this, val)
    }
  }
})

To conclude .set() is used to hook into non-default object behvaiour added by base, define() creates non-eunumerable, configurable objects. Non-enumerable means, these properties won't appear in keys list, on iterations or serializations.

const base = require('brisky-base')
const foo = base()
foo.set({ bar: true }) // → Base "bar"
foo.bar = true // → true (normal object)
foor.define({ bar: true }) // → non enumerable, configurable property descriptor

###Context Context enables deep memory efficient prototypes. Stores information on fields about first non-shared ancestors. The context syntax can be used to create mem efficient immutables for example

basic

Notice that base.a.b.c === instance.a.b.c is true but the paths are different

const base = require('brisky-base')

const obj = base({
  key: 'base'
  a: { b: { c: 'its c' } }
})
const instance = new obj.Constructor({
  key: 'instance'
})
console.log(base.a.b.c === instance.a.b.c) // → true
console.log(instance.a.b.c.path()) // → [ 'instance', 'a', 'b', 'c' ]
console.log(base.a.b.c.path()) // → [ 'base', 'a', 'b', 'c' ]

store and apply context

Allows storage and restoration of context. Useful for edge cases where you need to make a handle to a nested field in a certain context

Consists of 2 methods

  • applyContext(context)
  • storeContext()
const base = require('brisky-base')

const obj = base({
  key: 'base'
  a: { b: { c: 'its c' } }
})
const instance = new obj.Constructor({
  key: 'instance'
})
const b = instance.a.b
const context = b.storeContext()
console.log(obj.a.b.c) // this will remove the context "instance", and replace it with base
b.applyContext(context) // will reset the context of b to instance

Apply context can return 3 different types

  • undefined Context is restored without any differences
  • Base A set has happened in the path leading to the target of apply context
  • null A remove has happened in the path leading to the target of apply context

###Get Simple get api, useful when dealing with defaults

basic

const base = require('brisky-base')
const obj = base({ a: { b: { c: 'c!' } } })
var c = obj.get('a.b.c') // get c
c = obj.get(['a', 'b', 'c']) // also gets c

default

const base = require('brisky-base')
const obj = base({ a: { b: { c: 'c!' } } })
const d = obj.get('a.b.d', {}) // creates new property d with as a value an empty object
const c = obj.get('a.b.c', 'default!') // gets a.b.c, does not set it to "default!""

index

const base = require('brisky-base')
const obj = base({ a: { b: { c: 'c!' } } })
c = obj.get('[0][0][0]') // also get c (gets first key of every object)
c = obj.get('[-1][-1][-1]') // also get c (gets last key of every object)
c = obj.get('[2]') // returns undefined (tries to get the 3rd key)

###Keys

The .keys() method allows fast object iteration, sorting and filtering of properties

basic

const base = require('brisky-base')
const obj = base({ foo: {}, bar: {} })
console.log(obj.keys()) // → [ 'foo', 'bar' ]

keyType

const base = require('brisky-base')
const obj = base({
  foo: { keyType: 'special' }, // by setting keyType it will not show up in .keys()
  bar: true,
  baz: true,
})
console.log(obj.keys()) // → [ 'bar', 'baz' ] other keyTypes get filtered out
console.log(obj.keys('special')) // → [ 'foo' ]

keyFilter

Default filter used for keys, default of base requires the property to be a base object

const base = require('brisky-base')
const obj = base({
  foo: true,
  bar: true,
  baz: true,
  keyFilter: (key) => key[0] === 'b'
})
console.log(obj.keys()) // → [ 'bar', 'baz' ]

sort

As a key

const base = require('brisky-base')
const obj = base({
  sort: 'name',
  foo: { name: 'foo' },
  bar: { name: 'bar' },
  list: [ 5, 2, 1, 3, 4 ]
})
console.log(obj.keys()) // → [ 'bar', 'foo' ]

obj.list.set({ sort: 'val' }) // corresponds to the primitive value (val)
// val is the default field used in sort
console.log(obj.list.keys()) // → [ 2, 1, 3, 0, 4 ]

Replace the internal method, follows default js sort (default field is val)

const base = require('brisky-base')
const obj = base({
  sort: {
    exec: (a, b) => a < b ? 1 : a > b ? -1 : 0,
    val: 'val'
  },
  foo: 1,
  bar: 2
})
console.log(obj.keys()) // → [ 'bar', 'foo' ]

obj.set({ sort: (a, b) => a > b ? 1 : a < b ? -1 : 0 })
// reverse sort order
console.log(obj.keys()) // → [ 'foo', 'bar' ]

###Iteration

Base exposes as much standard apis for iteration as possible, .each is the exception

array

Like standard array methods, but iterating over base properties

map, filter, reduce, forEach

push

Similar to array push, but generates a key based on current time, does not support multiple arguments

const base = require('brisky-base')
base.push('hello') // creates a key based on `Date.now()`  → { 21321232323: 'hello' }
base.push('hello') // pushing at the same time adds a decimal → {   '21321232323.1': 'hello'  }
base.push('x', 111) // second argument is stamp, does not support multiple values

each

Loop trough values of a base object, differs slightly from forEach

const base = require('brisky-base')
const foo = base({
  a: {},
  b: {},
  c: { keyType: 'hello' }
})

foo.each(p => {
  console.log(p) // iterates over each key
  // returning a value in each will break the each loop and return the value
})

foo.each(p => {
  // iterates over keyType 'hello'
}, 'hello')

Returning a truthy value in each will break the each loop and return the value

const base = require('brisky-base')
const foo = base({
  a: {}, b: {}, c: {}
})
console.log(foo.each(p => p.key === 'b' ? 'hello' : false)) // → "hello"

###Types

Use the types api to reduce complexity of dealing with classes, prototypes and components. Especialy useful for composition when combined with inject

const base = require('brisky-base')

const articleModule = {
  types: {
    article: {
      text: 'some text',
      title: 'hello'
    }
  }
}

base({
  inject: articleModule,
  field: { type: 'article' }, // will make field into an instance of article,
  bla: { type: 'article' }, // will make bla into an instance of article
  nested: {
    types: {
      article: { text: 'lullz special article' },
    },
    something: { type: 'article' } // will get the fist "types.article" it can find in a parent
  }
})

base({
  types: {
    // types is just an object
    myType: { field: 'hello' }
  },
  bla: { type: 'myType' },
  hello: { type: 'base' } // base is a default type
})

Big advantage of this system is that it allows you to change types not as dependents but as extensions, bit similar to for example web components.

-

###Compute

Compute is used to return the computed value of a base object - this allows for special hooks and, for example function support

basic

const base = require('brisky-base')
const a = base(() => Date.now())
a.compute() // → "return the current date"

###References

Serializable references, this is handy for, for example dealing with server/client side or multiple processes.

basic

const base = require('brisky-base')
const a = base('a')
const b = base(a)
console.log(b.compute()) // → "a"
a.set('A!')
console.log(b.compute()) // → "A!"

string notation

const base = require('brisky-base')
const obj = base({
  a: '$root.b', // creates b on the root when its not there
  c: {
    d: '$.parent.parent.a', // gets a
    e: '$.parent.d' // gets the parent object "d"
  }
})
obj.set({ b: 'its b!' })
console.log(a.c.e.compute()) // → returns "its b!"

no reference

In some cases you may want to escape the behaviour of creating references and just set a base object as a field

const base = require('brisky-base')
const a = base({ noReference: true })
const obj = base({ a }) // obj.a === a (no reference)

###Serialize

Serialize base objects to normal objects, that you can set again

basic

const base = require('brisky-base')
const obj = base({
  a: 'a!',
  b: {
    val: 'b!',
    c: true
  }
})
obj.set({ d: obj.a })
obj.serialize() // → { a: 'a!', b: { val: 'b!', c: true }, d: '$root.a' }

compute

Computes values instead of making them into set objects

const base = require('brisky-base')
const obj = base({
  a: {
    val: 'hello',
    b: true
  },
  b: '$root.a'
})
obj.serialize(true) // → { a: 'hello', b: 'hello' }

filter

Filter certain fields that you, for example, don't want to send from a server to a client

const base = Base({
  yuzi: {
    james: {
      marcus: true,
      secret: true
    }
  }
})
base.serialize(false, (prop) => prop.key !== 'secret') // → { yuzi: { james: { marcus: true } } },

###Traversal

Base objects have properties and methods to make object traversal easier

parent

const base = require('brisky-base')
const obj = base({ a: { b: true } })
console.log(obj.a.b.parent === obj.a) // → true

root

const base = require('brisky-base')
const obj = base({ a: { b: true } })
console.log(obj.a.b.root === obj) // → true

key

const base = require('brisky-base')
const obj = base({ a: { b: true } })
console.log(obj.a.b.key) // → "b"

path

Generates a path array, works over context paths (virtual paths)

const base = require('brisky-base')
const obj = base({ a: { b: true } })
console.log(obj.a.b.path()) // → [ "a", "b" ]

lookUp

Look up to find if a parent has certain properties

const base = require('brisky-base')
const obj = base({
  a: { b: { c: true } },
  b: true
})
obj.a.b.c.lookUp('b') // → returns "obj.b"
obj.b.lookUp([ 'a', 'b', 'c' ]) // → returns "obj"

###Child

The child property is used to set default types for properties of a Base The default child of a base object is a base

object

const base = require('brisky-base')
const obj = base({
 child: { text: 'hello' },
 foo: {},
 bar: {}
})

console.log(obj.foo.text.compute()) // → "hello"
console.log(obj.bar.text.compute()) // → "hello"
// both fields are instances of the base.child

types

Mixit with types

const base = require('brisky-base')
const obj = base({
 types: { myType: { text: 'hello' } },
 child: { type: 'myType' },
 foo: {},
 bar: {}
})

console.log(obj.foo.text.compute()) // → "hello"
console.log(obj.bar.text.compute()) // → "hello"
// both fields are instances of the base.child

constructor

Using a string Constructor as a child value, means use myself for my children (creates deep inheritance)

const base = require('brisky-base')
const obj = base({
  child: {
    text: 'hello',
    child: 'Constructor'
  },
  x: { y: { z: true } }
})

console.log(obj.x.y.z.text.compute()) // → "hello"
// all fields (deep and shallow) inherit text: 'hello'

default behaviour

Using true as a child value, results in using normal objects / primitives for child values

const base = require('brisky-base')
const obj = base({
  child: true
  x: 'hello'
})

console.log(obj.x) // → "hello"
// all properties of obj that you set will become "normal" object properties

###Properties The properties field is used to add property definitions for certain keys within set objects.

There are 4 types of property definitions:

  • true clears any special base behaviour for the key
  • function calls the function when the key is set instead of the normal behaviour
  • null removes property definition and any existing instances
  • anything else uses the set function

basic

const base = require('brisky-base')
const foo = base({
  properties: {
    normal: true,
    special (val, stamp) {
      this.special = val * 10
    },
    base: { nested: true }
  }
})

foo.set({
  normal: 'hello', // → 'hello'
  special: 10, // → 100
  base: 'a base' // → Base { val: 'a base', nested: true }
})

foo.set({
  properties: {
    normal: null
    // removes property defintion and removes "normal"
  }
})

set

const base = require('brisky-base')

const special = base({
  type: 'special'
})

const obj = base({
  properties: {
    // uses "noReference" for a base
    special: special
  }
})

obj.set({
  special: 10 // → Special 10
})

// add something to the "special" property
obj.set({
  properties: {
    special: {
      aField: true
    }
  }
})
// → base.special.aField.val === true, inherits from the property

define

Allows for extra customisation of property definitions.

It has 3 options:

  • :key when a property is set uses this key to store its value
  • :reset resets a previously defined property
  • :val property value
const base = require('brisky-base')

const obj = base({
  properties: {
    define: {
      x: { key: 'y' },
      something: {
        key: 'else',
        val: {
          a: true,
          b: true
        }
      },
      hello: {
        key: 'bye',
        val: 100
      }
    }
  },
  x: 10 // → y: 10
  something: { c: true }, // → else: Base { a: true, b: true, c: true }
  hello: { field: true } // → bye: Base { val: 100, field: true }
})

obj.set({
  properties: {
    define: {
      something: {
        // removes the "else" field on base
        // creates a new property definition for "something"
        reset: true,
        val: 'hello'
      },
      x: {
        key: 'z',
        reset: false // will not move "y"
      },
      hello: {
        // moves "bye" → "hello"
        key: null
      }
    }
  }
})

###Examples

Some fun use cases

Create an immutable class

const Immutable = base({
  define: {
    change (val, stamp) { return new this.Constructor(val, stamp) }
  },
  child: 'Constructor'
}).Constructor

const state1 = new Immutable(10)
const state2 = state1.change(20)
const state3 = state3.change(30)