0.2 (2012-01-15)

Manages virtual file systems that can be easily transported over network. Buster uses resource collections to ship your source files and tests to buster-capture-server, but they can also be used for other purposes, like mixing files on disk and "virtual" files for build scripts and whatnot.

The central data types is the resource - which can be a file on disk, an in-memory "file" or an http proxy - and the resource set. A resource set is a collection of resources that allows the creation of e.g. combined resources, and can be serialized as a whole and transported over the network. Resource sets can be cached and served over http using objects in this module.

Resource middleware

var resourceMiddleware = require("buster-resources").resourceMiddleware

The resource middleware can serve resource sets over HTTP. In its simplest form, you spin up an instance, designate a context path to it, mount a resource set (only one can be served at any given time), and allow it to handle requests entirely on its own:

var http = require("http");
var rs = require("buster-resources");
var middleware = rs.resourceMiddleware.create("/resources");
var set = rs.resourceSet.create();
set.addResource({ path: "/buster.js", content: "Booyah!" });
http.createServer(function (req, res) {
    if (middleware.respond(req, res)) { return; }
// Test it
    host: "localhost",
    port: 9988,
    path: "/resources/buster.js"
}, function (res) {
    res.on("data", function (chunk) {

var middleware = resourceMiddleware.create(contextPath)

Create a new instance to serve resource sets. The middleware will only respond to requests within the context path. The context path is also stripped from the URL before finding resources.


Change the context path.


Serve contents of resource set. If another resource set is mounted, it will be dismounted.

var willRespond = middleware.respond(req, res)

Responds to an HTTP request, if the request is for a path within the middleware's context path. If the middleware intends to handle the request, this method returns true (even if the request may not have been handled synchronously). Otherwise, it returns false.

If the request is within the middleware's context path, but does not match any resources, the middleware will give a 404 response.

Typical usage

var http = require("http");
var resourceMiddleware = require("buster-resources").resourceMiddleware;
var middleware = resourceMiddleware.create("/resources");
// Mount sets
http.createServer(function (req, res) {
    if (middleware.respond(req, res)) { return; }
    // Handle requests not handled by the middleware

Resource cache

var resourceSetCache = require("buster-resources").resourceSetCache

Cache content across resource sets. The resource set cache works as a central repository that you pass resource sets by to have their contents cached, and their missing contents replenished from the cache.

var cache = resourceSetCache.create(ttl)

Creates a new cache. ttl decides for how many milliseconds individual resources are cached. The default time to live for resources is one hour. Note that the ttl only determines how long resources stay in the internal cache. Once you've inflated a resource set with a cached resource, it will stick around in that resource set until you remove it on your own.

var promise = cache.inflate(resourceSet)

Inflating a resource set achieves two things: 1) Any resource in the set that has an etag and content will be cached. 2) Any resource in the set that has an etag and whose content is empty, will be replaced with a cached copy, if one exists.

Note that the resource cache caches entire resources, not only content. To avoid having certain resources cached, simply make sure they don't have an etag set.

Serving resource sets with a cache

var http = require("http");
var rs = require("buster-resources");
var middleware = rs.resourceMiddleware.create("/resources");
var cache = rs.resourceSetCache.create(60 * 60 * 1000);
// Assume 'set' is a resourceSet instance
cache.inflate(set).then(function (inflatedSet) {

var result = cache.resourceVersions(resourceSet)

Returns an object with information about all path/etag combinations contained in the cache:

set.addResource({ path: "/buster.js", etag: "123", content: "OK" });
set2.addResource({ path: "/buster.js", etag: "abc", content: "Newer" });
when.all([cache.inflate(set1), cache.inflate(set2)], function () {
    cache.resourceVersions() === {
        "/buster.js": ["abc", "123"]

Resource sets

var resourceSet = require("buster-resources").resourceSet

A resource set lets you represent a set of files associated with paths. It lets you create bundles of multiple resources, proxy certain paths to other HTTP servers, preprocess resources (for example convert CoffeeScript into JavaScript), and more.

var promise = resourceSet.deserialize(data)

Deserialize a resource set. The data should be a JavaScript object, the kind that serialize produces. The method returns a promise that resolves with the fully inflated resource set.

Typically, when receiving resource sets over HTTP, they will be JSON encoded, bring it back to life like so:

var resourceSet = require("buster-resource").resourceSet;
// Assume 'data' holds a JSON encoded resource set serialization
resourceSet.deserialize(JSON.parse(data)).then(function (set) {
    // Serve set over HTTP or similar

var set = resourceSet.create(rootPath)

Creates a new resource set. The rootPath is used to resolve globs and direct file paths. If not provided, it defaults to the current working directory. You can not add files to a resource set if they live outside the resource set root directory.


The length of the resource set is the number of resources in it. Resource sets expose resources through an array-like interface with length and numeric properties.

var promise = rs.addResources(resources)

Adds multiple resources. Argument is an array of resources as accepted by addResource. The method returns a promise that resolves with an array of resources.

var promise = rs.addResource(resource)

Adds a resource. The argument can be either a proper resource instance, a string (either a file path or a glob, see addGlobResource) or an object with properties describing a resource (extended resource 'spec'). The method returns a promise that resolves with a single resource.

var promise = rs.addGlobResource(path)

Add all files matching the glob as resources. Returns a promise that resolves with an array of resources. The glob is resolved relatively to the resource set rootPath.

var promise = rs.addFileResources(paths, rs)

Add multiple files as resources with common meta data rs. Each path will be passed along with rs to addFileResource. Returns a promise that resolves with an array of resources.

var promise = rs.addFileResource(path, rs)

Adds a file as resource. The path is resolved against the resource set rootPath. You can provide the path to serve the resource through as part of the rs object. Returns a promise that resolves with a single resource.

var promise = rs.addCombinedResource(sources, rs)

Add a resource whos content is the combination of other resources in the set. sources is an array of paths to other pre-existing resources. Returns a promise that resolves with a single resource.

var resource = rs.get(path)

Returns the resource at path. The path will be normalized before lookup, so rs.get("buster.js") === rs.get("/buster.js");


Removes a resource with the given path. Will also remove it from loadPath if present.

var promise = rs.serialize()

Serializes the resource set. The serialization format is a plain JavaScript object with two properties: resources and load, both of which are arrays. The serialized object can safely be JSON encoded for wire transfer. The serialization will also have all resource contents loaded in a flat structure.

var newRs = rs.concat(rs2, rs3, ...)

Create a new resource set by combining this one with one or more other resource sets. Does not mutate any of the existing resource sets.


Append paths to the load path. Paths may be glob patterns. Any path does not match an existing resource in the resource set will be added from disk before added to the load path. This is different from calling append directly on the loadPath, where a missing resource causes an error.


Like appendLoad, only prepend to the load path in place of append.


An object that allows you to control what resources should be loaded when the resource set is loaded.

Resource set load path


Append paths to the end of the load path.


Prepend paths to the beginning of the load path.


Remove path from load path.


Remove all paths from load path.

var paths = loadPath.paths()

Returns an array of paths on the load path. This array is just a copy, and can not be used to mutate the load path.


Resource set payload

The resource set payload is an object that consists of a set of resources, and optionally a list of resources to automatically load in the root resource. TODO: Write about root resource and auto injection.

    resources: {
        "/path": {resource-payload},
        "/other-path": {resource-payload},
    load: [resourcePath, ...]


An object where the key is the path and the value is the resource payload. The equivalent of calling addResource().


List of paths to "load". The path must exist as a resource.

In buster-capture-server, the resources in load will be automatically injected as script tags before the closing </body> tag. A resource set does not in itself what it means to load something.

Resource payload

This section describes the object that is passed to resource creation, such as rs.addResource("/path", payload) and rs.addFile("/path/to/file", payload).

{content: "a string"}

Sets the content of the resource to the value of the string.

{content: new Buffer(...)}

Sets the content of the resource to the value of the buffer.

{content: function (promise) {}}

Function will be called when needed and allows for asynchronous fetching of content via a promise. This is what addFile uses under the hood.

It is imperative that you either resolve or reject the promise. There's no internal time out, so if you do networking or something else that could time out, you should create your own timeout and reject the promise when the timeout fires. You also need to make sure you don't accept the promise after you already rejected it, and vice versa.

rs.addResource("/foo", {
    content: function (promise) {
        // We don't do anything asynchronous here so we might as well
        // have used a string directly instead of a function.
        promise.resolve("This is the content");
rs.addResource("/foo", {
    content: function (promise) {
        fs.readFile("/foo", function (err, data) {
            if (err) {
            } else {
rs.addResource("/foo", {
    content: function (promise) {
            {host: "myserver.com", port: 80, path: "/test"},
            function (res) {
                var data = "";
                res.on("data", function (chunk) { data += chunk; });
                res.on("end", function () { promise.resolve(data); });

{headers:{"Header": "Value"}}

Set custom headers. A Content-Type header will be added automatically if not present, via the node-mime project.


The etag is used in combination with the name of the resource to determine wether the buster-capture-server already has this resource.

How the etag is calculated is entirely up to you. By convention, the only expectation is that if the file for which the resource points to has changed, the etag should change as well. Internally in buster, we calculate the etag by applying SHA1 to the mtime and the absolute path to the file.

TODO: write more about how to practically perform caching against buster-capture-server.

{backend: "url"}

A full URL to a http server that will be requested when the resource in question is requested.

The URLs will be rewritten based on the path to the resource itself. For rs.addResource("/foo", {backend: "http://foo.com"});, a call to rs.getResource("/foo/test", cb); will perform a request to http://foo.com/test.

When getResource is used, a plain HTTP request with no special request headers are performed.

When getResourceViaHttp is used, a mini proxy server will perform a HTTP request matching the incoming request.

{combine: ["/foo.js", "/bar.js"]}

Combines existing reseources into one resource. The resources passed have to exist before you create a combined resource for them.