A specification for stateless, promise-based, event emitting, javascript tasks.
WORK IN PROGRESS, FEEDBACK WELCOME
This project aims to define a stateless, promise-based, event emitting API for javascript tasks. Modules which adhere to this specification should be published as stand-alone packages on npm. The target for this spec includes file-based tasks, internal CLI front ends, and most exciting of all: plugins for the fantastic javascript build-tool Grunt (coming with version 0.5+).
Eventually, it is hoped that popular JS libraries will maintain their own node-task modules (think jshint, stylus, handlebars, etc). If/when this happens, it will be trivial to pass files through an arbitrary pipeline of interactions and transformations utilizing libraries across the entire npm ecosystem.
Perform an arbitrary task.
Modules which implement the basic node-task specification must provide a constructor which can be called to create a unique instance of an object with the following API.
≈ denotes an optional method or property.
A single word name for task.
A short description of the job the task will perform.
A valid semver string.
If a task allows options, they must be enumerated under this property as an object where the key is the option name and the value is an object which contains, at a minimum, key/value pairs for description
and default
. This property is primarily intended for task runner introspection, but authors are encouraged to use it for applying default values in parseConfig
.
An EventEmitter compatible on
method. In order to allow parallel execution by task runners, this method must assign listeners to a unique instance of the task.
An EventEmitter compatible off
method. In order to allow parallel execution by task runners, this method must remove listeners from a unique instance of the module.
An EventEmitter compatible emit
method. In order to allow parallel execution by task runners, this method must emit events for a unique instance of the module.
Execute a task. Must emit a run
event with config
as the first argument before processing task operations. A promise representing the completion of the task must be returned.
Normalize task configuration, returning the modified config with any with any defaults from options
applied. Before processing, must emit parseConfig
event with the source config
as the first argument. This method must be synchronous.
Pre-task operations, if any, occur here. Before processing, must emit setup
event with config
as the first argument.
Post-task operations, if any, occur here. Before processing, must emit teardown
event with config
as the first argument.
Note: While the following examples meet the requirements of the basic spec, they should not be considered the only correct way to implement a compliant module. Task runners will undoubtedly provide builders to facilitate the creation of tasks.
A minimal compliant module:
var when = require('when');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Task = function Task() {
EventEmitter.call(this);
};
util.inherits(Task, EventEmitter);
Task.prototype.run = function (config) {
this.emit('run', config);
return when(true);
};
module.exports = Task;
A more comprehensive implementation:
var when = require('when');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
var Task = function Task() {
EventEmitter.call(this);
};
util.inherits(Task, EventEmitter);
Task.prototype.name = 'example';
Task.prototype.description = 'fake task using all spec properties and methods';
Task.prototype.version = '0.1.0';
Task.prototype.options = {
debug: {
description: "debug mode",
default: false
},
fake: {
description: "a fake option",
default: 1
}
};
Task.prototype.parseConfig = function (config) {
this.emit('parseConfig', config);
var defaults = _.merge({}, this.options, function(d, o) {
return o.default;
});
return _.extend(defaults, config);
};
Task.prototype.run = function (config) {
var runConfig = this.parseConfig(config);
this.emit('setup', runConfig);
this.emit('run', runConfig);
this.emit('teardown', runConfig);
return when(true);
};
Task.prototype.setup = function (config) {
this.emit('debug', config);
};
Task.prototype.teardown = function (config) {
this.emit('teardown', config);
};
module.exports = Task;
The following standard events are available for task authors and logger implementers. totally incomplete
debug
- Debug mode logging.info
- Verbose mode logging.log
- Standard mode logging.success
- Standard mode logging.warn
- Standard mode non-critical error logging.error
- Standard mode critical error logging.
A buffer interface for piping data between tasks.
For tasks which operate on files, each input should be loaded using a node-task compliant buffer. A valid interface is a Node.js Buffer instance with the following additional APIs. Please see node-datapipe, node-filebuffer & node-s3buffer for actual implementations.
An property identifying the buffer's source: filepath, url, object, etc.
String property containing buffer's encoding.
Return a clone of the instance.
Fill buffer synchronously and return self for chaining. Input may be any Buffer, or a string which is valid for the instance's encoding.
Read contents of source into buffer and return a promise which resolves to self. If any additional parameters are required for loading (i.e. providing a s3 client), they can be passed in via opts. If the buffer already contains data this should immediately return a promise which resolves to self. This is a noop until extended (see node-filebuffer and node-s3buffer for examples).
Write contents of buffer to source and return a promise which resolves to self. If any additional parameters are required for saving (i.e. providing a s3 client, acl settings, etc), they can be passed via opts. This is a noop until extended (see node-filebuffer and node-s3buffer for examples).
Load data into buffer (if not already loaded) and process it with method
, yielding a promise which resolves to methods
's return value.