fmbaucis
Version:
Build scalable REST APIs using the open source tools and standards you already know.
157 lines (143 loc) • 4.88 kB
JavaScript
// __Dependencies__
var es = require('event-stream');
var crypto = require('crypto');
var BaucisError = require('../BaucisError');
// __Private Module Members__
// A map that is used to create empty response body.
function empty (context, callback) { callback(null, '') }
// Map contexts back into documents.
function redoc (context, callback) { callback(null, context.doc) }
// Generate a respone Etag from a context.
function etag (response) {
return es.map(function (context, callback) {
var hash = crypto.createHash('md5');
var etag = response.get('Etag');
if (etag) return callback(null, context);
hash.update(JSON.stringify(context.doc));
response.set('Etag', '"' + hash.digest('hex') + '"');
callback(null, context);
});
}
// Generate a Last-Modified header
function lastModified (response, lastModifiedPath) {
return es.map(function (context, callback) {
if (!response.get('Last-Modified') && lastModifiedPath) {
response.set('Last-Modified', context.doc.get(lastModifiedPath));
}
callback(null, context);
});
}
// Build a reduce stream.
function reduce (accumulated, f) {
return es.through(
function (context) {
accumulated = f(accumulated, context);
},
function () {
this.emit('data', accumulated);
this.emit('end');
}
);
}
// Count emissions.
function count () {
return reduce(0, function (a, b) { return a + 1 });
}
// __Module Definition__
var decorator = module.exports = function (options, protect) {
var controller = this;
// Create the basic stream.
protect.finalize(function (request, response, next) {
var count = 0;
var documents = request.baucis.documents;
var pipeline = request.baucis.send = protect.pipeline();
// If documents were set in the baucis hash, use them.
if (documents) pipeline(es.readArray([].concat(documents)));
// Otherwise, stream the relevant documents from Mongo, based on constructed query.
else pipeline(request.baucis.query.stream());
// Map documents to contexts.
pipeline(function (doc, callback) {
callback(null, { doc: doc, incoming: null });
});
// Check for 404.
pipeline(es.through(
function (context) {
count += 1;
this.emit('data', context);
},
function () {
if (count === 0) this.emit('error', BaucisError.NotFound());
else this.emit('end');
}
));
// Apply user streams.
pipeline(request.baucis.outgoing());
// Set the document formatter based on the Accept header of the request.
request.baucis.api.formatters(response, function (error, formatter) {
if (error) return next(error);
request.baucis.formatter = formatter;
next();
});
});
// HEAD
protect.finalize('instance', 'head', function (request, response, next) {
var modified = controller.lastModified();
if (modified) request.baucis.send(lastModified(response, modified));
request.baucis.send(etag(response));
request.baucis.send(empty);
next();
});
protect.finalize('collection', 'head', function (request, response, next) {
request.baucis.send(empty);
next();
});
// GET
protect.finalize('instance', 'get', function (request, response, next) {
var modified = controller.lastModified();
if (modified) request.baucis.send(lastModified(response, modified));
request.baucis.send(etag(response));
request.baucis.send(redoc);
request.baucis.send(request.baucis.formatter());
next();
});
protect.finalize('collection', 'get', function (request, response, next) {
if (request.baucis.count) {
request.baucis.send(count());
request.baucis.send(es.stringify());
}
else {
request.baucis.send(redoc);
request.baucis.send(request.baucis.formatter(true));
}
next();
});
// POST
protect.finalize('collection', 'post', function (request, response, next) {
request.baucis.send(redoc);
request.baucis.send(request.baucis.formatter());
next();
});
// PUT
protect.finalize('put', function (request, response, next) {
request.baucis.send(redoc);
request.baucis.send(request.baucis.formatter());
next();
});
// DELETE
protect.finalize('del', function (request, response, next) {
// Remove each document from the database.
request.baucis.send(function (context, callback) { context.doc.remove(callback) });
// Respond with the count of deleted documents.
request.baucis.send(count());
request.baucis.send(es.stringify());
next();
});
protect.finalize(function (request, response, next) {
var out = request.baucis.send();
out.on('error', function (error) {
if (error.message !== 'bad hint') return next(error);
next(BaucisError.BadRequest('The requested query hint is invalid'));
});
out.pipe(response);
});
};