workframe.js is a nascient Node.js micro-framework, which really means its just
a collection of simple utilities and ideas with the aim of making it easier to
develop complicated applications on Node.js. In particular it aims to solve
the problems of
workflow.js: managing deeply-nested callbacksurlpattern.js: regular expression based url dispatchforms.js: form validation
Although all of these utilities are intended to play well together, none of them share code or have any special awareness of each other; they are all completely decoupled.
workflow.js is the combination of a simple tool and a simple convention which together make it
possible to write imperative workflows in Node.js, which makes it possible to describe, modify and
maintain complicated workflows whose underlying implementation relies on callbacks.
Workflows are composed of one or more segments. Some example segments are sending an HTTP response to a client, rendering a Mustache template, or generating a new unique identifier in Redis.
Workflows are defined and started like this (usually the index function would be called by
the urlpattern.js dispatcher, but there isn't any programmatic coupling whatsoever):
exports.index = function(req, res) {
var ctx = {title:"Home"};
workflow.run([[redis.ids, "user.projects"],
redis.mget,
[utils.render_template, "index"],
[utils.http_response, req, res]],
ctx)};
When specifying segments you may either specify a function (as in the above example), or you may specify a list where the zeroth index contains a function and the remaining positions are parameters which will be passed as positional parameters to the specific component (this reduces reliance on injecting values into the workflow's context dictionary which is a very brittle mechanism for passing information, especially if a single segment is used multiple times in a workflow).
workflow.run([redis.mget, [utils.http_response, "index"]]);
workflow.run([[utils.http_redirect, "/"]]);
When you pass parameters this way, they always get injected as position parameters before the parameters passed by the preceeding function. (This is because usually you only specify parameters this way when the flow isn't really linear.)
Where each workflow segment is a function which looks like this:
exports.http_redirect = function(funs, ctx, location) {
if (location === undefined) location = ctx.redirect;
ctx.res.writeHead(303, {"Location":location});
ctx.res.close();
workflow.run(funs, ctx);
}
More examples at segments/. (Well, they will be there soon, anyway.)
These will be coming over the next few days.
urlpattern.js is a utility for doing regular expression based url dispatching.
It is modeled very closely off the Django file by the same name.
A simple example (which assumes workframe.js is checked out at ../workframe.js
relative to the my_project.js file).
// my_project.js
var http = require("http"),
views = require("./views"),
urlpattern = require("../workframe.js/urlpattern");
urlpattern.patterns = [
[/^\/project\/view\/([\w-_]+)\/$/, views.view_project],
[/^\/$/, views.index]
];
http.createServer(function(req, res) {
// if you aren't interested in capturing any
// incoming data, then you can remove the data
// variable and also the data listener and
// use urlpattern.dispatch(req, res) instead
var data = "";
req.addListener('data', function(chunk) {
data += chunk;
}
req.addListener('end', function() {
urlpattern.dispatch(req, res, data);
});
}).listen(8000);
// views.js
// 'data' is optional parameter, well, all parameters in JS are
// optional, but usually you don't want it, especially if you
// remove the data listener in your HTTP server
exports.index = function(req, res, data) {
res.writeHead(200, {'Content-Type': "text/html"});
res.write("this is index.html");
res.close();
}
// this could also be declared as
// exports.view_project = function(req, res, project_name) {
exports.view_project = function(req, res, project_name, data) {
res.writeHead(200, {'Content-Type': "text/html"});
res.write("this is view_project.html for project with name "+project_name);
res.close();
}
In addition to defining patterns for dispatch, it is also possible to override the
default page (to throw a custom 404 page, etc) by updating the value of urlpattern.default_404.
A valid default_404 function will look like this:
urlpatterns.default_404 = function(req, res) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.write('No matching pattern found');
res.close();
}
The forms stuff is really really young and experimental and will undoubtedly change as I keep working with it.
Relies on the forms.validate function which takes a dictionary
of raw data (probably from querystring.parse, but could be from
anywhere) and a form schema.
Form schemas are very simple, and look like this:
[{name:"title"}, {name:"age", type:"number", coerce:parseInt}]
coerce can be any function with an arity of one, and type can be
any value returned by the typeof function.
forms.validate returns a dictionary with two values errors and data.
data is the input data passed through any coerce functions and errors is
a dictionary where the key is the name of the field with an error and the value is
a specific error message.