Data models that can be stored in any database or remote service using adapters. It makes the semantic interoperability and standardization of data and web services possible.
var resource = require('tower-resource');
attributes:
resource('post')
.attr('title') // defaults to 'string'
.attr('body', 'text')
.attr('published', 'boolean', false);
validations:
resource('user')
.attr('email')
.validate('presence')
.validate('isEmail')
.validate('emailProvider', { in: [ 'gmail.com' ] }) // some hypothetical one
.attr('username')
.validator(function(val){
return !!val.match(/[a-zA-Z]/);
});
There are two DSL methods for validation.
validate
: for using predefined validations (see tower-validator), purely to clean up the API.validator
: for defining custom validator functions right inline. If you want to reuse your custom validator function across resources, just move the function into tower-validator.queries:
resource('post')
.where('published').eq(true)
.all(function(err, posts){
});
See tower-query for the entire syntax. Thewhere
method just delegates to aQuery
instance. You can also access the query object directly (it just adds.select(resourceName)
for you):
resource('post').query().sort('title', -1).all();
actions:
There are 4 main actions for resources (which are just delegated toquery().action(name)
:
resource('post').create();
resource('post').all();
resource('post').update({ published: true });
resource('post').remove();
Under the hood, when you execute one of these actions, they get handled by a database-/service-specific adapter (mongodb, cassandra, facebook, etc.). Those adapters can perform optimizations such as streaming query results back.
Datastore abstraction layer.
To abstract out some database like Cassandra, a REST API like Facebook, or even something like plain web crawling, so that it can be queried like any other resource, just implement theexec
method on a new adapter.
var adapter = require('tower-adapter');
See one of these for a complete example:
Example custom REST adapter implementing theexec
method:
/**
* Map of query actions to HTTP methods.
*/
var methods = {
find: 'GET',
create: 'POST',
update: 'PUT',
remove: 'DELETE'
};
adapter('rest').exec = function(query, fn){
var name = query.selects[0].resource;
var method = methods[query.type];
var params = serializeParams(query);
$.ajax({
url: '/api/' + name,
dataType: 'json',
type: method,
data: params,
success: function(data){
fn(null, data);
},
error: function(data){
fn(data);
}
});
};
/**
* Convert query constraints into query parameters.
*/
function serializeParams(query) {
var constraints = query.constraints;
var params = {};
constraints.forEach(function(constraint){
params[constraint.left.attr] = constraint.right.value;
});
return params;
}
Map REST API objects to resources:
adapter('facebook')
.model('user')
.attr('name')
.attr('firstName').from('first_name')
.attr('middleName').from('middle_name')
.attr('lastName').from('last_name')
.attr('gender')
.validate('in', [ 'female', 'male' ])
.attr('link')
.validate('isUrl')
.attr('username');
Specify (optional) how to serialize data types from JavaScript to the database-/service-specific format:
adapter('mongodb')
.type('string', fn)
.type('text', fn)
.type('date', fn)
.type('float', fn)
.type('integer', fn)
.type('number', fn)
.type('boolean', fn)
.type('bitmask', fn)
.type('array', fn);
Client-side reactive templates (just plain DOM node manipulation, no strings).
var template = require('tower-template');
var el = document.querySelector('#todos');
var fn = template(el);
fn({ some: 'data' }); // applies content to DOM directives.
API to the DOM. Tells the DOM what to do.
var directive = require('tower-directive');
directive('data-text', function(scope, element, attr){
element.textContent = scope[attr.value];
});
var content = { foo: 'Hello World' };
var element = document.querySelector('#example');
directive('data-text').exec(content, element);
example template:
<span id="example" data-text="foo"></span>
becomes:
<span id="example" data-text="foo">Hello World</span>
The directives are used more robustly in tower-template.
Data for the DOM.
var content = require('tower-content');
content('menu')
.attr('items', 'array')
.attr('selected', 'object')
.action('select', function(index){
this.selected = this.items[index];
});
content('menu').init({ items: [ 'a', 'b' ] }).select(1);
API for creating custom parsers (based on parsing expression grammars, PEGs).
This is a basic math example inspired from pegjs.
var expression = require('tower-expression');
expression('additive')
.match(':multiplicative', ' + ', ':additive', function(left, op, right){
return left + right;
})
.match(':multiplicative');
expression('multiplicative')
.match(':primary', ' * ', ':multiplicative', function(left, op, right){
return left * right;
})
.match(':primary');
expression('primary')
.match(':integer')
.match('(', ':additive', ')', function(l, additive, r){
return additive;
});
expression('integer')
.match(/[0-9]+/, function(digits){
return parseInt(digits, 10);
});
var integer = expression('additive').parse('(7 + 8) * 2');
console.log(integer); // 15
One place expressions are used is in directives, for parsing statments, such as:
<li data-each="user in users">{{user.username}}</li>
<div data-text="loggedIn ? 'Profile' : 'Log in"></div>
There are many applications for expressions, such as custom search input expressions (like google/twitter have), custom macros, building fast client-side syntax highlighters, etc.
Tiny route component for client and server.
var route = require('tower-route');
route('/welcome', function(context, next){
context.render({ title: 'Hello World' });
});
Or with theaction
method:
route('/welcome')
.action(function(context, next){
context.render({ title: 'Hello World' });
});
You can also name them (makes it so you don't have to mess with url strings in code, to redirect/transition/etc.:
route('new-customer', '/welcome');
var router = require('tower-router');
router.start();
API for defining/sanitizing custom resource attribute types.
var type = require('tower-type');
Define comparators/validators for basic types:
type('string')
.validator('gte', function gte(a, b){
return a.length >= b.length;
})
.validator('gt', function gt(a, b){
return a.length > b.length;
});
Define a custom type with custom validators:
var now = Date.parse('2013-05-01');
type('birthdate')
.validator('can-drive', function(val){
return now >= val;
});
var validate = type.validator('birthdate.can-drive');
validate(Date.parse('1950-12-21')); // true
Sanitize values:
type('digits')
.use(stripWhitespace)
.use(stripLetters);
type('digits').sanitize(' 1 foo b2a3r'); // 123
function stripWhitespace(val) {
return val.replace(/\s+/g, '');
}
function stripLetters(val) {
return val.replace(/[a-z]+/g, '');
}
Minimal, extensible validation component.
var validator = require('tower-validator');
validator('eq', function eq(a, b){
return a === b;
});
validator('neq', function neq(a, b){
return a !== b;
});
validator('gte', function gte(a, b){
return a >= b;
});
validator('gt', function gte(a, b){
return a > b;
});
I18n, inflections, and other random bits of text to keep organized.
var text = require('tower-text');
text('messages')
.one('You have 1 message')
.past('You had 1 message')
.future('You might get a message')
.none('You have no messages')
.past('You never had any messages')
.future('You might never get a message')
.other('You have {{count}} messages')
.past('You had {{count}} messages')
.future('You might get {{count}} messages');
assert(9 === text('messages').inflections.length);
// 1
assert('You have 1 message' === text('messages').render({ count: 1 }));
assert('You had 1 message' === text('messages').render({ tense: 'past', count: 1 }));
assert('You might get a message' === text('messages').render({ tense: 'future', count: 1 }));
// 0
assert('You have no messages' === text('messages').render({ count: 0 }));
assert('You never had any messages' === text('messages').render({ tense: 'past', count: 0 }));
assert('You might never get a message' === text('messages').render({ tense: 'future', count: 0 }));
// n
assert('You have 3 messages' === text('messages').render({ count: 3 }));
assert('You had 3 messages' === text('messages').render({ tense: 'past', count: 3 }));
assert('You might get 3 messages' === text('messages').render({ tense: 'future', count: 3 }));
$ tower create ec2:server