2.2.0 • Published 9 years ago

covenance v2.2.0

Weekly downloads
2
License
ISC
Repository
github
Last release
9 years ago

covenance

Build Status

Join the chat at https://gitter.im/yangmillstheory/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't undefined
  • 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

2.2.0

9 years ago

2.1.1

9 years ago

2.1.0

9 years ago

2.0.2

9 years ago

2.0.0

9 years ago

1.1.5

9 years ago

1.1.4

9 years ago

1.1.3

9 years ago

1.1.2

9 years ago

1.1.1

9 years ago

1.1.0

9 years ago

1.0.2

9 years ago

1.0.1

9 years ago

1.0.0

9 years ago