brisky-base
v4.0.7
Published
Extendable object constructors, build for speed, low memory consumption and simplicity
Downloads
154
Readme
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
###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 differencesBase
A set has happened in the path leading to the target of apply contextnull
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
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 keyfunction
calls the function when the key is set instead of the normal behaviournull
removes property definition and any existing instancesanything 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)