README
covenance
Abstract base and covenanted classes in JavaScript.
Developed in ES6 & Babel, tested with tape, built with gulpjs, distributed via NPM.
Concepts
Covenant
A Covenant
is a specification for a valid object property.
It's defined by two required read-only attributes: attribute
and validator
. These are the property name, and the property validator, respectively.
An covenanted object obj
satisfies a Covenant
covenant
if and only if
obj[covenant.attribute]
isn'tundefined
covenant.validator(obj[covenant.attribute])
is truthy
If either of these fail to hold, the object property is in an invalid state, and errors will be thrown when covenant checks are invoked.
If an optional excstring
attribute is provided and obj[attribute]
is defined, any covenant error thrown will have it as its message
.
covenance
If a Function
and/or its prototype has a property covenance
that is an
Array
of Covenants
(the result of calling this method), then that function can be covenanted.
Covenanting the function gives it a method called check_covenants
that validates
the Covenants
that exist on the function and/or its prototype.
ABCMeta
It's often useful for a collection of classes to share Covenants
- this gives
us the notion of an Abstract Base Class, or ABC. See this article for more information.
covenance provides a way of creating such classes - which are modeled as subclasses
of the immutable type ABCMeta
.
Install
$ npm install --save covenance
Usage
Import the module:
import {covenance} from 'covenance'
covenance.covenant(Function fn)
Covenant a class:
import {is_number} from './utilities'
// validates 'covenance' property exists on the anonymous class
// and/or its prototype, then mixes in check_covenants method
let Point = covenance.covenant(class {
get covenance() {
return covenance.of(
{attribute: 'x', validator: is_number, excstring: 'Coordinate must be a number'},
{attribute: 'y', validator: is_number, excstring: 'Coordinate must be a number'}
)
}
constructor(x, y) {
this.x = x;
this.y = y;
this.check_covenants()
}
})
new Point(1, 'string') // throws CovenantBroken("Coordinate must be a number")
Pre/post covenanting hooks fire if covenance
exists on prototype and/or Function
.
In the hook body, this
is the prototype or Function
.
covenance.covenant(Point, {
pre_covenant() {
// fires before 'covenance' property check and adding 'check_covenants'
}
})
covenance.covenant(Point, {
post_covenant() {
// fires after 'covenance' property check and adding 'check_covenants'
// 'this' is the prototype or function
}
})
Pre/post covenant check hooks fire before/after a check_covenants
invocation.
In the hook body, this
is the prototype or Function
.
covenance.covenant(Point, {
pre_check_covenants() {
// fires before a check_covenants() invocation
}
})
covenance.covenant(Point, {
post_check_covenants() {
// fires after a check_covenants() invocation
}
})
Covenants can be specified on the prototype and function simultaneously, the hook API is the same.
Each hook will be invoked for each context (prototype or function) that has covenance
. In invocations, this
will point to either the prototype or function.
ABC(...)
Create an abstract base class.
import {is_string, is_function, is_number} from './utilities'
import {ABC} from 'covenance'
let MyABC = ABC({
name: 'MyABC',
proto: {
covenance: covenance.of(
{attribute: 'proto1', validator: is_string},
{attribute: 'proto2', validator: is_function}
),
// optional abstract implementations, subclasses can call up
props: {
proto1: 'proto1',
proto2() {
return 'proto2'
}
}
},
klass: {
covenance: covenance.of(
{attribute: 'static1', validator: is_number},
{attribute: 'static2', validator: is_function}
),
// optional abstract implementations, subclasses can call up
props: {
static1: 999
}
}
});
MyABC.name // 'MyABC'
new MyABC() // Error - can't instantiate abstract class
Implement the ABC and register the implementation.
class Impl {
get proto1() {
return `Impl_proto1_${this._proto1}`'
}
proto2() {
return super.proto2()
}
static get static1() {
return super.static1 + 1000
}
static static2() {
return 'Impl_static2'
}
}
// removing any of the properties above will cause this to throw
//
// this call verifies that Impl satisfies the covenance
// and causes Impl to inherit from MyABC
MyABC.implementation(Impl)
MyABC.implemented_by(Impl) // returns true
Since implementation(Function fn)
returns fn
, a smoother way to write this is
let Impl = MyABC.implementation(class Impl {
get proto1() {
return `Impl_proto1_${this._proto1}`'
}
proto2() {
return super.proto2()
}
static get static1() {
return super.static1 + 1000
}
static static2() {
return 'Impl_static2'
}
});
Implementations of an ABC must satisfy all Covenants
in the prototype and/or class covenance
, even if the ABC provides its own implementations.
As demonstrated above, however, implementations can utilize the ABC implementations in their own.
API
covenance.of(...)
Returns an immutable Array
of Covenants
for covenanting classes.
Each positional argument can either be an object {attribute: [String], validator: [Function], excstring: [String|undefined]}
or a
tuple [[String] attribute, [Function] validator]
.
If excstring
is provided and is a String
, it'll be the message
of any validation errors thrown when attribute
is present on the covenanted obj
and validator(obj[attribute])
returns false
.
This is the only way to create a valid covenance
on a Function
or its prototype.
covenance.covenant(Function fn, [Object options])
Register Covenants
on a function fn
. fn
must have a covenance
property defined on itself
or its prototype.
Adds a check_covenants() method to fn
.
Options can be an object with any combination of keys pre_covenant
, post_covenant
,
pre_check_covenants
, post_check_covenants
mapping to functions, as discussed above
here and here.
Returns fn
.
Aliases: assert
, execute
fn.check_covenants()
Only available for functions that have been covenanted.
Validates that the covenance
is satisfied in fn
and/or fn.prototype
.
CovenantBroken
A subclass of TypeError
that's thrown during {Function fn}.check_covenants
whenever a Covenant
is broken.
Has an attribute
property that's the String
name of the invalid property. message
property is configurable when creating covenants.
ABC(Object spec)
Return a subclass of ABCMeta
. The provided spec should include a String
name
,
and either a proto
object or klass
object with a covenance
key mapping to an Array
of Covenants
.
proto
and/or klass
can each contain a props
object that will be copied into
the prototype of the ABC and/or itself.
In addition to any props
, the ABC will have a registration method.
{ABCMeta MyABC}.implementation(Function fn)
Call this whenever you implement an ABCMeta
to ensure that its covenance
is satisfied.
It throws CovenantBroken
in case your implementation isn't valid, and causes fn
to inherit from MyABC
in the ES6 sense.
Aliases: register
{ABCMeta MyABC}.implemented_by(Function fn)
Returns true if and only if fn
is a valid implementation of MyABC, in the above sense.
Note that MyABC
stores weak references to all its implementations, so this will return false
if fn
is removed from the heap.
See the tests, and the discussion above for more detail.
Contributing
Development is in snake_case
ES6.
Get the source.
$ git clone git@github.com:yangmillstheory/covenance
Install dependencies.
$ npm install
Compile sources.
$ node_modules/.bin/gulp build
Run tests.
$ npm test
License
MIT © 2015, Victor Alvarez