<commit_before>
/*!
 * Connect - HTTPServer
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var http = require('http')
  , parse = require('url').parse
  , assert = require('assert');

// prototype

var app = module.exports = {};

// environment

var env = process.env.NODE_ENV || 'development';

/**
 * Utilize the given middleware `handle` to the given `route`,
 * defaulting to _/_. This "route" is the mount-point for the
 * middleware, when given a value other than _/_ the middleware
 * is only effective when that segment is present in the request's
 * pathname.
 *
 * For example if we were to mount a function at _/admin_, it would
 * be invoked on _/admin_, and _/admin/settings_, however it would
 * not be invoked for _/_, or _/posts_.
 *
 * This is effectively the same as passing middleware to `connect.createServer()`,
 * however provides a progressive api.
 *
 * Examples:
 *
 *      var server = connect();
 *      server.use(connect.favicon());
 *      server.use(connect.logger());
 *      server.use(connect.static(__dirname + '/public'));
 *
 * If we wanted to prefix static files with _/public_, we could
 * "mount" the `static()` middleware:
 *
 *      server.use('/public', connect.static(__dirname + '/public'));
 *
 * This api is chainable, meaning the following is valid:
 *
 *      connect.createServer()
 *        .use(connect.favicon())
 *        .use(connect.logger())
 *        .use(connect.static(__dirname + '/public'))
 *        .listen(3000);
 *
 * @param {String|Function} route or handle
 * @param {Function} handle
 * @return {Server}
 * @api public
 */

app.use = function(route, handle){
  this.route = '/';

  // default route to '/'
  if ('string' != typeof route) {
    handle = route;
    route = '/';
  }

  // wrap sub-apps
  if ('function' == typeof handle.handle) {
    var server = handle;
    handle.route = route;
    handle = function(req, res, next){
      server.handle(req, res, next);
    };
  }

  // wrap vanilla http.Servers
  if (handle instanceof http.Server) {
    handle = handle.listeners('request')[0];
  }

  // normalize route to not trail with slash
  if ('/' == route[route.length - 1]) {
    route = route.substr(0, route.length - 1);
  }

  // add the middleware
  this.stack.push({ route: route, handle: handle });

  // allow chaining
  return this;
};

/**
 * Handle server requests, punting them down
 * the middleware stack.
 *
 * @api private
 */

app.handle = function(req, res, out) {
  var writeHead = res.writeHead
    , stack = this.stack
    , removed = ''
    , index = 0;

  function next(err) {
    var layer, path, status, c;
    req.url = removed + req.url;
    req.originalUrl = req.originalUrl || req.url;
    removed = '';

    // next(status) support
    if ('number' == typeof err) {
      status = err;
      err = new Error(http.STATUS_CODES[status]);
      err.status = status;
    }

    // next callback
    layer = stack[index++];

    // all done
    if (!layer || res.headerSent) {
      // delegate to parent
      if (out) return out(err);

      // unhandled error
      if (err) {
        // default to 500
        if (res.statusCode < 400) res.statusCode = 500;

        // respect err.status
        if (err.status) res.statusCode = err.status;

        // production gets a basic error message
        var msg = 'production' == env
          ? http.STATUS_CODES[res.statusCode]
          : err.stack || err.toString();

        // log to stderr in a non-test env
        if ('test' != env) console.error(err.stack || err.toString());
        if (res.headerSent) return req.socket.destroy();
        res.setHeader('Content-Type', 'text/plain');
        res.setHeader('Content-Length', Buffer.byteLength(msg));
        res.end(msg);
      } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Cannot ' + req.method + ' ' + req.url);
      }
      return;
    }

    try {
      path = parse(req.url).pathname;
      if (undefined == path) path = '/';

      // skip this layer if the route doesn't match.
      if (0 != path.indexOf(layer.route)) return next(err);

      c = path[layer.route.length];
      if (c && '/' != c && '.' != c) return next(err);

      // Call the layer handler
      // Trim off the part of the url that matches the route
      removed = layer.route;
      req.url = req.url.substr(removed.length);

      // Ensure leading slash
      if ('/' != req.url[0]) req.url = '/' + req.url;

      var arity = layer.handle.length;
      if (err) {
        if (arity === 4) {
          layer.handle(err, req, res, next);
        } else {
          next(err);
        }
      } else if (arity < 4) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    } catch (e) {
      if (e instanceof assert.AssertionError) {
        console.error(e.stack + '\n');
        next(e);
      } else {
        next(e);
      }
    }
  }
  next();
};
<commit_msg>Fixed HEAD support for 404s and 500s<commit_after>
/*!
 * Connect - HTTPServer
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var http = require('http')
  , parse = require('url').parse
  , assert = require('assert');

// prototype

var app = module.exports = {};

// environment

var env = process.env.NODE_ENV || 'development';

/**
 * Utilize the given middleware `handle` to the given `route`,
 * defaulting to _/_. This "route" is the mount-point for the
 * middleware, when given a value other than _/_ the middleware
 * is only effective when that segment is present in the request's
 * pathname.
 *
 * For example if we were to mount a function at _/admin_, it would
 * be invoked on _/admin_, and _/admin/settings_, however it would
 * not be invoked for _/_, or _/posts_.
 *
 * This is effectively the same as passing middleware to `connect.createServer()`,
 * however provides a progressive api.
 *
 * Examples:
 *
 *      var server = connect();
 *      server.use(connect.favicon());
 *      server.use(connect.logger());
 *      server.use(connect.static(__dirname + '/public'));
 *
 * If we wanted to prefix static files with _/public_, we could
 * "mount" the `static()` middleware:
 *
 *      server.use('/public', connect.static(__dirname + '/public'));
 *
 * This api is chainable, meaning the following is valid:
 *
 *      connect.createServer()
 *        .use(connect.favicon())
 *        .use(connect.logger())
 *        .use(connect.static(__dirname + '/public'))
 *        .listen(3000);
 *
 * @param {String|Function} route or handle
 * @param {Function} handle
 * @return {Server}
 * @api public
 */

app.use = function(route, handle){
  this.route = '/';

  // default route to '/'
  if ('string' != typeof route) {
    handle = route;
    route = '/';
  }

  // wrap sub-apps
  if ('function' == typeof handle.handle) {
    var server = handle;
    handle.route = route;
    handle = function(req, res, next){
      server.handle(req, res, next);
    };
  }

  // wrap vanilla http.Servers
  if (handle instanceof http.Server) {
    handle = handle.listeners('request')[0];
  }

  // normalize route to not trail with slash
  if ('/' == route[route.length - 1]) {
    route = route.substr(0, route.length - 1);
  }

  // add the middleware
  this.stack.push({ route: route, handle: handle });

  // allow chaining
  return this;
};

/**
 * Handle server requests, punting them down
 * the middleware stack.
 *
 * @api private
 */

app.handle = function(req, res, out) {
  var writeHead = res.writeHead
    , stack = this.stack
    , removed = ''
    , index = 0;

  function next(err) {
    var layer, path, status, c;
    req.url = removed + req.url;
    req.originalUrl = req.originalUrl || req.url;
    removed = '';

    // next(status) support
    if ('number' == typeof err) {
      status = err;
      err = new Error(http.STATUS_CODES[status]);
      err.status = status;
    }

    // next callback
    layer = stack[index++];

    // all done
    if (!layer || res.headerSent) {
      // delegate to parent
      if (out) return out(err);

      // unhandled error
      if (err) {
        // default to 500
        if (res.statusCode < 400) res.statusCode = 500;

        // respect err.status
        if (err.status) res.statusCode = err.status;

        // production gets a basic error message
        var msg = 'production' == env
          ? http.STATUS_CODES[res.statusCode]
          : err.stack || err.toString();

        // log to stderr in a non-test env
        if ('test' != env) console.error(err.stack || err.toString());
        if (res.headerSent) return req.socket.destroy();
        res.setHeader('Content-Type', 'text/plain');
        res.setHeader('Content-Length', Buffer.byteLength(msg));
        if ('HEAD' == req.method) return res.end();
        res.end(msg);
      } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        if ('HEAD' == req.method) return res.end();
        res.end('Cannot ' + req.method + ' ' + req.url);
      }
      return;
    }

    try {
      path = parse(req.url).pathname;
      if (undefined == path) path = '/';

      // skip this layer if the route doesn't match.
      if (0 != path.indexOf(layer.route)) return next(err);

      c = path[layer.route.length];
      if (c && '/' != c && '.' != c) return next(err);

      // Call the layer handler
      // Trim off the part of the url that matches the route
      removed = layer.route;
      req.url = req.url.substr(removed.length);

      // Ensure leading slash
      if ('/' != req.url[0]) req.url = '/' + req.url;

      var arity = layer.handle.length;
      if (err) {
        if (arity === 4) {
          layer.handle(err, req, res, next);
        } else {
          next(err);
        }
      } else if (arity < 4) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    } catch (e) {
      if (e instanceof assert.AssertionError) {
        console.error(e.stack + '\n');
        next(e);
      } else {
        next(e);
      }
    }
  }
  next();
};
<|endoftext|>


<commit_before><commit_msg>proper get functionality<commit_after><|endoftext|>


<commit_before>const { createGlobalProxyAgent } = require('global-agent');

module.exports = {
  bootstrap,
};

function bootstrap() {
  return createGlobalProxyAgent({
    environmentVariableNamespace: '',
  });
}
<commit_msg>fix(proxy): massage proxy env to upper case<commit_after>const { createGlobalProxyAgent } = require('global-agent');

module.exports = {
  bootstrap,
};

function bootstrap() {
  process.env.HTTP_PROXY = process.env.HTTP_PROXY || process.env.http_proxy;
  process.env.HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.https_proxy;
  process.env.NO_PROXY = process.env.NO_PROXY || process.env.no_proxy;
  return createGlobalProxyAgent({
    environmentVariableNamespace: '',
  });
}
<|endoftext|>


<commit_before><commit_msg>makin prettier<commit_after><|endoftext|>


<commit_before><commit_msg>Updated startup message.<commit_after><|endoftext|>


<commit_before>// From Node.js test/mjsunit/test-assert.js
// Felix Geisendörfer (felixge), backported from NodeJS
// Karl Guertin (greyrest), backported from NodeJS
// Kris Kowal (kriskowal), conversion to CommonJS

// strangely meta, no?

var assert = require('assert');

function makeBlock(f) {
    var args = Array.slice(arguments, 1);
    return function(){
        return f.apply(this, args);
    }
}

exports['test AssertionError instanceof Error'] = function () {
    assert.ok(new assert.AssertionError({}) instanceof Error);
};

exports['test ok false'] = function () {
    assert['throws'](makeBlock(assert.ok, false), assert.AssertionError);
};

exports['test ok(true)'] = makeBlock(assert.ok, true);
exports['test ok("test")'] = makeBlock(assert.ok, "test");
exports['test equal true false'] = function () {
    assert['throws'](makeBlock(assert.equal, true, false), assert.AssertionError, 'equal');
};

exports['test equal null null'] = makeBlock(assert.equal, null, null);
exports['test equal undefined undefined'] = makeBlock(assert.equal, undefined, undefined);
exports['test equal null undefined'] = makeBlock(assert.equal, null, undefined);
exports['test equal 2 "2"'] = makeBlock(assert.equal, 2, "2");
exports['test equal "2" 2'] = makeBlock(assert.equal, "2", 2);
exports['test equal true true'] = makeBlock(assert.equal, true, true);
exports['test notEqual true false'] = makeBlock(assert.notEqual, true, false);
exports['test notEqual true true'] = function () {
    assert['throws'](makeBlock(assert.notEqual, true, true), assert.AssertionError, 'notEqual');
};
exports['test strictEqual 2 "2"'] = function () {
    assert['throws'](makeBlock(assert.strictEqual, 2, "2"), assert.AssertionError, 'strictEqual');
};
exports['test strictEqual null undefined'] = function () {
    assert['throws'](makeBlock(assert.strictEqual, null, undefined), assert.AssertionError, 'strictEqual');
};
exports['test notStrictEqual 2 "2"'] = makeBlock(assert.notStrictEqual, 2, "2");

//deepEquals

//7.2
exports['test 7.2 deepEqual date'] = makeBlock(assert.deepEqual, new Date(2000,3,14), new Date(2000,3,14));
exports['test 7.2 deepEqual date negative'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, new Date(), new Date(2000,3,14)), assert.AssertionError, 'deepEqual date');
};

//7.3
exports['test 7.3 deepEqual 4 "4"'] = makeBlock(assert.deepEqual, 4, "4");
exports['test 7.3 deepEqual "4" 4'] = makeBlock(assert.deepEqual, "4", 4);
exports['test 7.3 deepEqual true 1'] = makeBlock(assert.deepEqual, true, 1);
exports['test 7.3 deepEqual 4 "5"'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, 4, "5"));
};

//7.4
// having the same number of owned properties && the same set of keys
exports['test 7.4 deepEqual {a:4} {a:4}'] = makeBlock(assert.deepEqual, {a:4}, {a:4});
exports['test 7.4 deepEqual {a:4,b:"2"} {a:4,b:"2"}'] = makeBlock(assert.deepEqual, {a:4,b:"2"}, {a:4,b:"2"});
exports['test 7.4 deepEqual [4] ["4"]'] = makeBlock(assert.deepEqual, [4], ["4"]);
exports['test 7.4 deepEqual {a:4} {a:4,b:true}'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, {a:4}, {a:4,b:true}), assert.AssertionError);
};

// NOTE: this fails due to different implementation of 7.4 (instead of
// comparing prototype property using Object.getPrototypeOf())
exports['test deepEqual ["a"], {0:"a"}'] = makeBlock(assert.deepEqual, ["a"], {0:"a"});

//(although not necessarily the same order),
exports['test deepEqual {a:4,b:"1"} {b:"1",a:4}'] = makeBlock(assert.deepEqual, {a:4,b:"1"}, {b:"1",a:4});

exports['test deepEqual arrays with non-numeric properties'] = function () {
    var a1 = [1,2,3];
    var a2 = [1,2,3];
    a1.a = "test";
    a1.b = true;
    a2.b = true;
    a2.a = "test"
    assert['throws'](makeBlock(assert.deepEqual, Object.keys(a1), Object.keys(a2)), assert.AssertionError);
    makeBlock(assert.deepEqual, a1, a2);
};

exports['test deepEqual identical prototype'] = function () {
    // having an identical prototype property
    var nbRoot = {
        toString: function(){return this.first+' '+this.last;}
    }
    var nameBuilder = function(first,last){
        this.first = first;
        this.last = last;
        return this;
    }
    nameBuilder.prototype = nbRoot;
    var nameBuilder2 = function(first,last){
        this.first = first;
        this.last = last;
        return this;
    }
    nameBuilder2.prototype = nbRoot;
    var nb1 = new nameBuilder('Ryan', 'Dahl');
    var nb2 = new nameBuilder2('Ryan','Dahl');

    assert.deepEqual(nb1, nb2);

    nameBuilder2.prototype = new Object();
    nb2 = new nameBuilder2('Ryan','Dahl');
    assert['throws'](makeBlock(assert.deepEqual, nb1, nb2), assert.AssertionError);

};

exports['test deepEqual "a" {}'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, 'a', {}), assert.AssertionError);
};

exports['test deepEqual "" ""'] = function () {
    assert.deepEqual("", "");
};

// NOTE: this fails due to 7.3 (narwhal implements a special case
// for this, see also known spec bugs at http://wiki.commonjs.org/wiki/Unit_Testing)
exports['test deepEqual "" [""]'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, '', ['']), assert.AssertionError);
};

exports['test deepEqual [""] [""]'] = function () {
    assert.deepEqual([""], [""]);
};

exports['test throw AssertionError'] = function () {

    //Testing the throwing
    function thrower(errorConstructor){
        throw new errorConstructor('test');
    }
    var aethrow = makeBlock(thrower, assert.AssertionError);
    var aethrow = makeBlock(thrower, assert.AssertionError);
    //the basic calls work
    assert['throws'](makeBlock(thrower, assert.AssertionError), assert.AssertionError, 'message');
    assert['throws'](makeBlock(thrower, assert.AssertionError), assert.AssertionError);
    assert['throws'](makeBlock(thrower, assert.AssertionError));
    //if not passing an error, catch all.
    assert['throws'](makeBlock(thrower, TypeError));
    //when passing a type, only catch errors of the appropriate type
    // NOTE: this fails, the spec doesn't say anything only catching expected errors
    var threw = false;
    try {
        assert['throws'](makeBlock(thrower, TypeError), assert.AssertionError);
    } catch (e) {
        threw = true;
        assert.ok(e instanceof TypeError, 'type');
    }
    assert.ok(threw, 'assert.throws with an explicit error is eating extra errors');
    threw = false;

};

if (module == require.main)
    require("test").run(exports);
<commit_msg>Temporarily disable vague CommonJS Unit Testing/1.0 compliance tests<commit_after>// From Node.js test/mjsunit/test-assert.js
// Felix Geisendörfer (felixge), backported from NodeJS
// Karl Guertin (greyrest), backported from NodeJS
// Kris Kowal (kriskowal), conversion to CommonJS

// strangely meta, no?

var assert = require('assert');

function makeBlock(f) {
    var args = Array.slice(arguments, 1);
    return function(){
        return f.apply(this, args);
    }
}

exports['test AssertionError instanceof Error'] = function () {
    assert.ok(new assert.AssertionError({}) instanceof Error);
};

exports['test ok false'] = function () {
    assert['throws'](makeBlock(assert.ok, false), assert.AssertionError);
};

exports['test ok(true)'] = makeBlock(assert.ok, true);
exports['test ok("test")'] = makeBlock(assert.ok, "test");
exports['test equal true false'] = function () {
    assert['throws'](makeBlock(assert.equal, true, false), assert.AssertionError, 'equal');
};

exports['test equal null null'] = makeBlock(assert.equal, null, null);
exports['test equal undefined undefined'] = makeBlock(assert.equal, undefined, undefined);
exports['test equal null undefined'] = makeBlock(assert.equal, null, undefined);
exports['test equal 2 "2"'] = makeBlock(assert.equal, 2, "2");
exports['test equal "2" 2'] = makeBlock(assert.equal, "2", 2);
exports['test equal true true'] = makeBlock(assert.equal, true, true);
exports['test notEqual true false'] = makeBlock(assert.notEqual, true, false);
exports['test notEqual true true'] = function () {
    assert['throws'](makeBlock(assert.notEqual, true, true), assert.AssertionError, 'notEqual');
};
exports['test strictEqual 2 "2"'] = function () {
    assert['throws'](makeBlock(assert.strictEqual, 2, "2"), assert.AssertionError, 'strictEqual');
};
exports['test strictEqual null undefined'] = function () {
    assert['throws'](makeBlock(assert.strictEqual, null, undefined), assert.AssertionError, 'strictEqual');
};
exports['test notStrictEqual 2 "2"'] = makeBlock(assert.notStrictEqual, 2, "2");

//deepEquals

//7.2
exports['test 7.2 deepEqual date'] = makeBlock(assert.deepEqual, new Date(2000,3,14), new Date(2000,3,14));
exports['test 7.2 deepEqual date negative'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, new Date(), new Date(2000,3,14)), assert.AssertionError, 'deepEqual date');
};

//7.3
exports['test 7.3 deepEqual 4 "4"'] = makeBlock(assert.deepEqual, 4, "4");
exports['test 7.3 deepEqual "4" 4'] = makeBlock(assert.deepEqual, "4", 4);
exports['test 7.3 deepEqual true 1'] = makeBlock(assert.deepEqual, true, 1);
exports['test 7.3 deepEqual 4 "5"'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, 4, "5"));
};

//7.4
// having the same number of owned properties && the same set of keys
exports['test 7.4 deepEqual {a:4} {a:4}'] = makeBlock(assert.deepEqual, {a:4}, {a:4});
exports['test 7.4 deepEqual {a:4,b:"2"} {a:4,b:"2"}'] = makeBlock(assert.deepEqual, {a:4,b:"2"}, {a:4,b:"2"});
exports['test 7.4 deepEqual [4] ["4"]'] = makeBlock(assert.deepEqual, [4], ["4"]);
exports['test 7.4 deepEqual {a:4} {a:4,b:true}'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, {a:4}, {a:4,b:true}), assert.AssertionError);
};

// NOTE: this fails due to different implementation of 7.4 (instead of
// comparing prototype property using Object.getPrototypeOf())
/* -- Temporarily disabled until this issue is resolved. --
exports['test deepEqual ["a"], {0:"a"}'] = makeBlock(assert.deepEqual, ["a"], {0:"a"});
*/

//(although not necessarily the same order),
exports['test deepEqual {a:4,b:"1"} {b:"1",a:4}'] = makeBlock(assert.deepEqual, {a:4,b:"1"}, {b:"1",a:4});

exports['test deepEqual arrays with non-numeric properties'] = function () {
    var a1 = [1,2,3];
    var a2 = [1,2,3];
    a1.a = "test";
    a1.b = true;
    a2.b = true;
    a2.a = "test"
    assert['throws'](makeBlock(assert.deepEqual, Object.keys(a1), Object.keys(a2)), assert.AssertionError);
    makeBlock(assert.deepEqual, a1, a2);
};

exports['test deepEqual identical prototype'] = function () {
    // having an identical prototype property
    var nbRoot = {
        toString: function(){return this.first+' '+this.last;}
    }
    var nameBuilder = function(first,last){
        this.first = first;
        this.last = last;
        return this;
    }
    nameBuilder.prototype = nbRoot;
    var nameBuilder2 = function(first,last){
        this.first = first;
        this.last = last;
        return this;
    }
    nameBuilder2.prototype = nbRoot;
    var nb1 = new nameBuilder('Ryan', 'Dahl');
    var nb2 = new nameBuilder2('Ryan','Dahl');

    assert.deepEqual(nb1, nb2);

    nameBuilder2.prototype = new Object();
    nb2 = new nameBuilder2('Ryan','Dahl');
    assert['throws'](makeBlock(assert.deepEqual, nb1, nb2), assert.AssertionError);

};

exports['test deepEqual "a" {}'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, 'a', {}), assert.AssertionError);
};

exports['test deepEqual "" ""'] = function () {
    assert.deepEqual("", "");
};

// NOTE: this fails due to 7.3 (narwhal implements a special case
// for this, see also known spec bugs at http://wiki.commonjs.org/wiki/Unit_Testing)
/* -- Temporarily disabled until this issue is resolved. --
exports['test deepEqual "" [""]'] = function () {
    assert['throws'](makeBlock(assert.deepEqual, '', ['']), assert.AssertionError);
};
*/

exports['test deepEqual [""] [""]'] = function () {
    assert.deepEqual([""], [""]);
};

exports['test throw AssertionError'] = function () {

    //Testing the throwing
    function thrower(errorConstructor){
        throw new errorConstructor('test');
    }
    var aethrow = makeBlock(thrower, assert.AssertionError);
    var aethrow = makeBlock(thrower, assert.AssertionError);
    //the basic calls work
    assert['throws'](makeBlock(thrower, assert.AssertionError), assert.AssertionError, 'message');
    assert['throws'](makeBlock(thrower, assert.AssertionError), assert.AssertionError);
    assert['throws'](makeBlock(thrower, assert.AssertionError));
    //if not passing an error, catch all.
    assert['throws'](makeBlock(thrower, TypeError));
    //when passing a type, only catch errors of the appropriate type
    // NOTE: this fails, the spec doesn't say anything only catching expected errors
    var threw = false;
    try {
        assert['throws'](makeBlock(thrower, TypeError), assert.AssertionError);
    } catch (e) {
        threw = true;
        /* -- Temporarily disabled until this issue is resolved. --
        assert.ok(e instanceof TypeError, 'type');
        */
    }
    assert.ok(threw, 'assert.throws with an explicit error is eating extra errors');
    threw = false;

};

if (module == require.main)
    require("test").run(exports);
<|endoftext|>


<commit_before>import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER } from '../actions/ActionTypes';
import { VisibilityFilters } from '../constants';

const { SHOW_ALL } = VisibilityFilters;

function todos(state, action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ];
    case COMPLETE_TODO:
      return [
        ...state.slice(0, action.index),
        Object.assign({}, state[action.index], {
          completed: true
        }),
        ...state.slice(action.index + 1)
      ];
    default:
      return state;
  }
}

function visibilityFilter(state, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter;
    default:
      return state;
  }
}

const initialState = {
  todos: [],
  visibilityFilter: SHOW_ALL,
};

export default function(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
    case COMPLETE_TODO:
     return Object.assign({}, state, { todos: todos(state.todos, action) });
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, { visibilityFilter: visibilityFilter(state.visibilityFilter, action) });
    default:
      return state;
  }
}
<commit_msg>Nest combineReducers at todo reducer.<commit_after>import { combineReducers } from 'redux';

import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER } from '../actions/ActionTypes';
import { VisibilityFilters } from '../constants';

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ];
    case COMPLETE_TODO:
      return [
        ...state.slice(0, action.index),
        Object.assign({}, state[action.index], {
          completed: true
        }),
        ...state.slice(action.index + 1)
      ];
    default:
      return state;
  }
}

function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter;
    default:
      return state;
  }
}

export default combineReducers({
  todos,
  visibilityFilter,
});
<|endoftext|>


<commit_before>'use strict';
var U = require('lodash');
var DynamoDB = require('./dynamodb');

function Query() {}

module.exports = Query;

U.extend(Query.prototype, {
	// spec.dynamodb
	// spec.tableName
	// spec.indexName
	// spec.hashkey
	// spec.rangekey
	// spec.rangeval
	// spec.rangeval2
	// spec.rangeExpression
	// spec.scanForward
	// spec.limit
	initialize: function (spec) {
		Object.defineProperties(this, {
			dynamodb: {
				value: spec.dynamodb
			},
			tableName: {
				enumerable: true,
				value: spec.tableName
			},
			indexName: {
				enumerable: true,
				value: spec.indexName || null
			},
			hashkey: {
				enumerable: true,
				value: spec.hashkey
			},
			rangekey: {
				enumerable: true,
				value: spec.rangekey
			},
			hashval: {
				enumerable: true,
				value: spec.hashval
			},
			rangeval: {
				enumerable: true,
				value: spec.rangeval
			},
			rangeval2: {
				enumerable: true,
				value: spec.rangeval2
			},
			rangeExpression: {
				enumerable: true,
				value: spec.rangeExpression || null
			},
			scanForward: {
				enumerable: true,
				value: U.has(spec, 'scanForward') ? spec.scanForward : true
			},
			limit: {
				enumerable: true,
				value: spec.limit || 20
			}
		});

		return this;
	},

	hashEqual: function (val) {
		return this.extend({
			hashval: val
		});
	},

	rangeEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey = :rangeval'
		});
	},

	rangeLessThan: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey < :rangeval'
		});
	},

	rangeLessThanOrEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey <= :rangeval'
		});
	},

	rangeGreaterThan: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey > :rangeval'
		});
	},

	rangeGreaterThanOrEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey >= :rangeval'
		});
	},

	rangeBetween: function (a, b) {
		return this.extend({
			rangeval: a,
			rangeval2: b,
			rangeExpression: '#rangekey BETWEEN :rangeval AND :rangeval2'
		});
	},

	rangeBeginsWith: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: 'begins_with(#rangekey, :rangeval)'
		});
	},

	ascending: function () {
		return this.extend({
			scanForward: true
		});
	},

	descending: function () {
		return this.extend({
			scanForward: false
		});
	},

	limit: function (val) {
		return this.extend({
			limit: val
		});
	},

	// params.debug
	fetch: function (params) {
		params = params || Object.create(null);

		var attributeNames = {
			'#hashkey': this.hashkey
		};
		var values = {
			':hashval': DynamoDB.valueDefinition(this.hashval)
		};

		if (!U.isUndefined(this.rangeval)) {
			attributeNames['#rangekey'] = this.rangekey;
			values[':rangeval'] = DynamoDB.valueDefinition(this.rangeval);
		}
		if (!U.isUndefined(this.rangeval2)) {
			values[':rangeval2'] = DynamoDB.valueDefinition(this.rangeval2);
		}

		var expression = '#hashkey = :hashval';
		if (this.rangeExpression) {
			expression += (' AND ' + this.rangeExpression);
		}

		var newParams = {
			TableName: this.tableName,
			Select: 'ALL_ATTRIBUTES',
			KeyConditionExpression: expression,
			ScanIndexForward: this.scanForward,
			Limit: this.limit,
			// ExclusiveStartKey: startKey,
			ExpressionAttributeNames: attributeNames,
			ExpressionAttributeValues: values
			// Potentially useful parameters:
			// ReturnConsumedCapacity
		};

		if (this.indexName) {
			newParams.IndexName = this.indexName;
		}

		return this.dynamodb
			.query(newParams)
			.then(function deserializeData(res) {
				// { Items: [
				//   { id: { S: '41530' },
				//     issueNumber: { N: '0' },
				//     pageCount: { N: '136' },
				//     title: { S: 'Ant-Man: So (Trade Paperback)' },
				//     type: { S: 'Comic' },
				//     upc: { S: 'EMPTY' },
				//     description: { S: 'Its the origin of the original Avenger' },
				//     modified: { S: '2012-09-25T18:05:58-0400' } }
				//   ],
				//   Count: 20,
				//   ScannedCount: 20,
				//   LastEvaluatedKey:
				//    { type: { S: 'Comic' },
				//      id: { S: '55288' },
				//      modified: { S: '2015-04-02T15:50:47-0400' }
				//    }
				// }
				return res.Items.map(DynamoDB.deserializeData);
			});
	},

	extend: function (params) {
		// Copy existing properties to a new blank Object, then extend it with the
		// new params before passing into the constructor.
		var spec = U.extend(U.extend(Object.create(null), this), params);
		spec.dynamodb = this.dynamodb;
		var obj = new Query();
		return obj.initialize(spec);
	}
});

// spec.dynamodb
// spec.tableName
// spec.indexName
// spec.hashkey
// spec.rangekey
Query.create = function (spec) {
	var obj = new Query();
	return obj.initialize(spec);
};
<commit_msg>Adds forgotten parameter to inline docs<commit_after>'use strict';
var U = require('lodash');
var DynamoDB = require('./dynamodb');

function Query() {}

module.exports = Query;

U.extend(Query.prototype, {
	// spec.dynamodb
	// spec.tableName
	// spec.indexName
	// spec.hashkey
	// spec.hashval
	// spec.rangekey
	// spec.rangeval
	// spec.rangeval2
	// spec.rangeExpression
	// spec.scanForward
	// spec.limit
	initialize: function (spec) {
		Object.defineProperties(this, {
			dynamodb: {
				value: spec.dynamodb
			},
			tableName: {
				enumerable: true,
				value: spec.tableName
			},
			indexName: {
				enumerable: true,
				value: spec.indexName || null
			},
			hashkey: {
				enumerable: true,
				value: spec.hashkey
			},
			rangekey: {
				enumerable: true,
				value: spec.rangekey
			},
			hashval: {
				enumerable: true,
				value: spec.hashval
			},
			rangeval: {
				enumerable: true,
				value: spec.rangeval
			},
			rangeval2: {
				enumerable: true,
				value: spec.rangeval2
			},
			rangeExpression: {
				enumerable: true,
				value: spec.rangeExpression || null
			},
			scanForward: {
				enumerable: true,
				value: U.has(spec, 'scanForward') ? spec.scanForward : true
			},
			limit: {
				enumerable: true,
				value: spec.limit || 20
			}
		});

		return this;
	},

	hashEqual: function (val) {
		return this.extend({
			hashval: val
		});
	},

	rangeEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey = :rangeval'
		});
	},

	rangeLessThan: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey < :rangeval'
		});
	},

	rangeLessThanOrEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey <= :rangeval'
		});
	},

	rangeGreaterThan: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey > :rangeval'
		});
	},

	rangeGreaterThanOrEqual: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: '#rangekey >= :rangeval'
		});
	},

	rangeBetween: function (a, b) {
		return this.extend({
			rangeval: a,
			rangeval2: b,
			rangeExpression: '#rangekey BETWEEN :rangeval AND :rangeval2'
		});
	},

	rangeBeginsWith: function (val) {
		return this.extend({
			rangeval: val,
			rangeExpression: 'begins_with(#rangekey, :rangeval)'
		});
	},

	ascending: function () {
		return this.extend({
			scanForward: true
		});
	},

	descending: function () {
		return this.extend({
			scanForward: false
		});
	},

	limit: function (val) {
		return this.extend({
			limit: val
		});
	},

	// params.debug
	fetch: function (params) {
		params = params || Object.create(null);

		var attributeNames = {
			'#hashkey': this.hashkey
		};
		var values = {
			':hashval': DynamoDB.valueDefinition(this.hashval)
		};

		if (!U.isUndefined(this.rangeval)) {
			attributeNames['#rangekey'] = this.rangekey;
			values[':rangeval'] = DynamoDB.valueDefinition(this.rangeval);
		}
		if (!U.isUndefined(this.rangeval2)) {
			values[':rangeval2'] = DynamoDB.valueDefinition(this.rangeval2);
		}

		var expression = '#hashkey = :hashval';
		if (this.rangeExpression) {
			expression += (' AND ' + this.rangeExpression);
		}

		var newParams = {
			TableName: this.tableName,
			Select: 'ALL_ATTRIBUTES',
			KeyConditionExpression: expression,
			ScanIndexForward: this.scanForward,
			Limit: this.limit,
			// ExclusiveStartKey: startKey,
			ExpressionAttributeNames: attributeNames,
			ExpressionAttributeValues: values
			// Potentially useful parameters:
			// ReturnConsumedCapacity
		};

		if (this.indexName) {
			newParams.IndexName = this.indexName;
		}

		return this.dynamodb
			.query(newParams)
			.then(function deserializeData(res) {
				// { Items: [
				//   { id: { S: '41530' },
				//     issueNumber: { N: '0' },
				//     pageCount: { N: '136' },
				//     title: { S: 'Ant-Man: So (Trade Paperback)' },
				//     type: { S: 'Comic' },
				//     upc: { S: 'EMPTY' },
				//     description: { S: 'Its the origin of the original Avenger' },
				//     modified: { S: '2012-09-25T18:05:58-0400' } }
				//   ],
				//   Count: 20,
				//   ScannedCount: 20,
				//   LastEvaluatedKey:
				//    { type: { S: 'Comic' },
				//      id: { S: '55288' },
				//      modified: { S: '2015-04-02T15:50:47-0400' }
				//    }
				// }
				return res.Items.map(DynamoDB.deserializeData);
			});
	},

	extend: function (params) {
		// Copy existing properties to a new blank Object, then extend it with the
		// new params before passing into the constructor.
		var spec = U.extend(U.extend(Object.create(null), this), params);
		spec.dynamodb = this.dynamodb;
		var obj = new Query();
		return obj.initialize(spec);
	}
});

// spec.dynamodb
// spec.tableName
// spec.indexName
// spec.hashkey
// spec.rangekey
Query.create = function (spec) {
	var obj = new Query();
	return obj.initialize(spec);
};
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2018  Zaid Abdulla
 *
 * This file is part of GenieACS.
 *
 * GenieACS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * GenieACS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with GenieACS.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";

const common = require("./common");

function stringToRegexp(input, flags) {
  if (input.indexOf("*") === -1) return false;

  let output = input.replace(/[[\]\\^$.|?+()]/, "\\$&");
  if (output[0] === "*") output = output.replace(/^\*+/g, "");
  else output = "^" + output;

  if (output[output.length - 1] === "*") output = output.replace(/\*+$/g, "");
  else output = output + "$";

  output = output.replace(/[*]/, ".*");
  return new RegExp(output, flags);
}

function normalize(input) {
  if (common.typeOf(input) === common.STRING_TYPE) {
    const vals = [input];
    const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
    if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });

    if (+input === parseFloat(input)) vals.push(+input);

    const d = new Date(input);
    if (input.length >= 8 && d.getFullYear() > 1983) vals.push(d);

    const r = stringToRegexp(input);
    if (r !== false) vals.push({ $regex: r });

    return vals;
  }
  return input;
}

function expandValue(value) {
  if (common.typeOf(value) === common.ARRAY_TYPE) {
    let a = [];
    for (const j of value) a = a.conact(expandValue(j));
    return [a];
  } else if (common.typeOf(value) !== common.OBJECT_TYPE) {
    const n = normalize(value);
    if (common.typeOf(n) !== common.ARRAY_TYPE) return [n];
    else return n;
  }

  const objs = [];
  const indices = [];
  const keys = [];
  const values = [];
  for (const [k, v] of Object.entries(value)) {
    keys.push(k);
    values.push(expandValue(v));
    indices.push(0);
  }

  let i = 0;
  while (i < indices.length) {
    const obj = {};
    for (let j = 0; j < keys.length; ++j) obj[keys[j]] = values[j][indices[j]];
    objs.push(obj);

    for (i = 0; i < indices.length; ++i) {
      indices[i] += 1;
      if (indices[i] < values[i].length) break;
      indices[i] = 0;
    }
  }
  return objs;
}

function permute(param, val) {
  const conditions = [];
  const values = expandValue(val);

  if (param[param.lastIndexOf(".") + 1] !== "_") param += "._value";

  for (const v of values) {
    const obj = {};
    obj[param] = v;
    conditions.push(obj);
  }

  return conditions;
}

function expand(query) {
  const newQuery = {};
  for (const [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      // Operator
      newQuery[k] = v.map(e => expand(e));
    } else {
      const conditions = permute(k, v);
      if (conditions.length > 1) {
        newQuery["$and"] = newQuery["$and"] || [];
        if (v && (v["$ne"] != null || v["$not"] != null)) {
          if (Object.keys(v).length > 1)
            throw new Error("Cannot mix $ne or $not with other operators");
          for (const c of conditions) newQuery["$an"].push(c);
        } else {
          newQuery["$and"].push({ $or: conditions });
        }
      } else {
        Object.assign(newQuery, conditions[0]);
      }
    }
  }

  return newQuery;
}

function testExpressions(params, expressions, lop) {
  for (const f of expressions) {
    const res = test(params, f);
    switch (lop) {
      case "$and":
        if (!res) return false;
        break;
      case "$or":
        if (res) return true;
        break;
      case "$nor":
        if (res) return false;
    }
  }

  switch (lop) {
    case "$and":
      return true;
    case "$or":
      return false;
    case "$nor":
      return true;
    default:
      throw new Error("Unknown logical operator");
  }
}

function test(params, query) {
  let res;
  for (const [k, v] of Object.entries(query)) {
    if (k.charAt(0) === "$") {
      // Logical operator
      res = testExpressions(params, v, k);
    } else {
      const value = params[k];
      if (common.typeOf(v) !== common.OBJECT_TYPE) {
        if (common.typeOf(value) === common.ARRAY_TYPE) {
          // TODO comparing array to regex, array to array, and object to object
          res = value.indexOf(v) !== -1;
        } else {
          if (common.typeOf(v) === common.REGEXP_TYPE) res = v.test(value);
          else res = v === value;
        }
      } else {
        for (const [k2, v2] of Object.entries(v)) {
          switch (k2) {
            case "$ne":
              if (common.typeOf(value) === common.ARRAY_TYPE)
                res = value.indexOf(v2) === -1;
              else res = value !== v2;
              break;
            case "$lt":
              res = value < v2;
              break;
            case "$lte":
              res = value <= v2;
              break;
            case "$gt":
              res = value > v2;
              break;
            case "$gte":
              res = value >= v2;
              break;
            case "$regex":
              res = v2.test(value);
              break;
            case "$in":
              throw new Error("Operator not supported");
            case "$nin":
              throw new Error("Operator not supported");
            case "$all":
              throw new Error("Operator not supported");
            case "$exists":
              throw new Error("Operator not supported");
            default:
              throw new Error("Operator not supported");
          }
        }
      }
    }

    if (!res) return false;
  }
  return true;
}

function matchType(src, dst) {
  const type = typeof src;
  if (type === "string") {
    return String(dst);
  } else if (type === "number") {
    if (+dst === parseFloat(dst)) return +dst;
    else if (!isNaN(Date.parse(dst))) return Date.parse(dst);
  } else if (type === "boolean") {
    const v = String(dst)
      .trim()
      .toLowerCase();
    if (v === "true" || v === "1") return true;
    else if (v === "false" || v === "0") return false;
  }
  return dst;
}

function testFilter(obj, filter) {
  for (const [k, v] of Object.entries(filter)) {
    const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
    const val = matchType(obj[param], v);
    switch (op) {
      case "=":
      case undefined:
        if (obj[param] !== val) return false;
        break;
      case ">":
        if (!(obj[param] > val)) return false;
        break;
      case ">=":
        if (!(obj[param] >= val)) return false;
        break;
      case "<":
        if (!(obj[param] < val)) return false;
        break;
      case "<=":
        if (!(obj[param] <= val)) return false;
        break;
      case "!":
        if (obj[param] === val) return false;
        break;
      default:
        throw new Error(`Unrecognized operator ${op}`);
    }
  }
  return true;
}

function convertMongoQueryToFilters(query, filters) {
  filters = filters || {};
  for (let [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      if (k === "$and")
        for (const vv of v) convertMongoQueryToFilters(vv, filters);
      else throw new Error(`Operator ${k} not supported`);
    } else if (k === "_tags") {
      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (let [kk, vv] of Object.entries(v)) {
          vv = vv.replace(/[^a-zA-Z0-9-]+/g, "_");
          if (kk === "$ne") filters[`Tags.${vv}!`] = true;
          else throw new Error(`Operator ${kk} not supported`);
        }
      } else {
        v = v.replace(/[^a-zA-Z0-9-]+/g, "_");
        filters[`Tags.${v}`] = true;
      }
    } else {
      switch (k) {
        case "_id":
          k = "DeviceID.ID";
          break;
        case "_deviceId._Manufacturer":
          k = "DeviceID.Manufacturer";
          break;
        case "_deviceId._OUI":
          k = "DeviceID.OUI";
          break;
        case "_deviceId._ProductClass":
          k = "DeviceID.ProductClass";
          break;
        case "_deviceId._SerialNumber":
          k = "DeviceID.SerialNumber";
          break;
        case "_lastInform":
          k = "Events.Inform";
          break;
        case "_lastBootstrap":
          k = "Events.0_BOOTSTRAP";
          break;
        case "_lastBoot":
          k = "Events.1_BOOT";
          break;
        case "_registered":
          k = "Events.Registered";
      }

      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (const [kk, vv] of Object.entries(v)) {
          switch (kk) {
            case "$eq":
              filters[k] = vv;
              break;
            case "$ne":
              filters[`${k}!`] = vv;
              break;
            case "$lt":
              filters[`${k}<`] = vv;
              break;
            case "$lte":
              filters[`${k}<=`] = vv;
              break;
            case "$gt":
              filters[`${k}>`] = vv;
              break;
            case "$gte":
              filters[`${k}>=`] = vv;
              break;
            default:
              throw new Error(`Oprator ${kk} not supported`);
          }
        }
      } else {
        filters[k] = v;
      }
    }
  }

  return filters;
}

function sanitizeQueryTypes(query, types) {
  for (const [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      // Logical operator
      for (const vv of v) sanitizeQueryTypes(vv, types);
    } else if (k in types) {
      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (const [kk, vv] of Object.entries(v)) {
          switch (kk) {
            case "$in":
            case "$nin":
              for (let i = 0; i < vv.length; ++i) vv[i] = types[k](vv[i]);
              break;
            case "$eq":
            case "$gt":
            case "$gte":
            case "$lt":
            case "$lte":
            case "$ne":
              v[kk] = types[k](vv);
              break;
            case "$exists":
            case "$type":
              // Ignore
              break;
            default:
              throw new Error("Operator not supported");
          }
        }
      } else {
        query[k] = types[k](query[k]);
      }
    }
  }

  return query;
}

exports.expand = expand;
exports.test = test;
exports.sanitizeQueryTypes = sanitizeQueryTypes;
exports.convertMongoQueryToFilters = convertMongoQueryToFilters;
exports.testFilter = testFilter;
<commit_msg>Fix typo in query.js<commit_after>/**
 * Copyright 2013-2018  Zaid Abdulla
 *
 * This file is part of GenieACS.
 *
 * GenieACS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * GenieACS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with GenieACS.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";

const common = require("./common");

function stringToRegexp(input, flags) {
  if (input.indexOf("*") === -1) return false;

  let output = input.replace(/[[\]\\^$.|?+()]/, "\\$&");
  if (output[0] === "*") output = output.replace(/^\*+/g, "");
  else output = "^" + output;

  if (output[output.length - 1] === "*") output = output.replace(/\*+$/g, "");
  else output = output + "$";

  output = output.replace(/[*]/, ".*");
  return new RegExp(output, flags);
}

function normalize(input) {
  if (common.typeOf(input) === common.STRING_TYPE) {
    const vals = [input];
    const m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
    if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });

    if (+input === parseFloat(input)) vals.push(+input);

    const d = new Date(input);
    if (input.length >= 8 && d.getFullYear() > 1983) vals.push(d);

    const r = stringToRegexp(input);
    if (r !== false) vals.push({ $regex: r });

    return vals;
  }
  return input;
}

function expandValue(value) {
  if (common.typeOf(value) === common.ARRAY_TYPE) {
    let a = [];
    for (const j of value) a = a.concat(expandValue(j));
    return [a];
  } else if (common.typeOf(value) !== common.OBJECT_TYPE) {
    const n = normalize(value);
    if (common.typeOf(n) !== common.ARRAY_TYPE) return [n];
    else return n;
  }

  const objs = [];
  const indices = [];
  const keys = [];
  const values = [];
  for (const [k, v] of Object.entries(value)) {
    keys.push(k);
    values.push(expandValue(v));
    indices.push(0);
  }

  let i = 0;
  while (i < indices.length) {
    const obj = {};
    for (let j = 0; j < keys.length; ++j) obj[keys[j]] = values[j][indices[j]];
    objs.push(obj);

    for (i = 0; i < indices.length; ++i) {
      indices[i] += 1;
      if (indices[i] < values[i].length) break;
      indices[i] = 0;
    }
  }
  return objs;
}

function permute(param, val) {
  const conditions = [];
  const values = expandValue(val);

  if (param[param.lastIndexOf(".") + 1] !== "_") param += "._value";

  for (const v of values) {
    const obj = {};
    obj[param] = v;
    conditions.push(obj);
  }

  return conditions;
}

function expand(query) {
  const newQuery = {};
  for (const [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      // Operator
      newQuery[k] = v.map(e => expand(e));
    } else {
      const conditions = permute(k, v);
      if (conditions.length > 1) {
        newQuery["$and"] = newQuery["$and"] || [];
        if (v && (v["$ne"] != null || v["$not"] != null)) {
          if (Object.keys(v).length > 1)
            throw new Error("Cannot mix $ne or $not with other operators");
          for (const c of conditions) newQuery["$an"].push(c);
        } else {
          newQuery["$and"].push({ $or: conditions });
        }
      } else {
        Object.assign(newQuery, conditions[0]);
      }
    }
  }

  return newQuery;
}

function testExpressions(params, expressions, lop) {
  for (const f of expressions) {
    const res = test(params, f);
    switch (lop) {
      case "$and":
        if (!res) return false;
        break;
      case "$or":
        if (res) return true;
        break;
      case "$nor":
        if (res) return false;
    }
  }

  switch (lop) {
    case "$and":
      return true;
    case "$or":
      return false;
    case "$nor":
      return true;
    default:
      throw new Error("Unknown logical operator");
  }
}

function test(params, query) {
  let res;
  for (const [k, v] of Object.entries(query)) {
    if (k.charAt(0) === "$") {
      // Logical operator
      res = testExpressions(params, v, k);
    } else {
      const value = params[k];
      if (common.typeOf(v) !== common.OBJECT_TYPE) {
        if (common.typeOf(value) === common.ARRAY_TYPE) {
          // TODO comparing array to regex, array to array, and object to object
          res = value.indexOf(v) !== -1;
        } else {
          if (common.typeOf(v) === common.REGEXP_TYPE) res = v.test(value);
          else res = v === value;
        }
      } else {
        for (const [k2, v2] of Object.entries(v)) {
          switch (k2) {
            case "$ne":
              if (common.typeOf(value) === common.ARRAY_TYPE)
                res = value.indexOf(v2) === -1;
              else res = value !== v2;
              break;
            case "$lt":
              res = value < v2;
              break;
            case "$lte":
              res = value <= v2;
              break;
            case "$gt":
              res = value > v2;
              break;
            case "$gte":
              res = value >= v2;
              break;
            case "$regex":
              res = v2.test(value);
              break;
            case "$in":
              throw new Error("Operator not supported");
            case "$nin":
              throw new Error("Operator not supported");
            case "$all":
              throw new Error("Operator not supported");
            case "$exists":
              throw new Error("Operator not supported");
            default:
              throw new Error("Operator not supported");
          }
        }
      }
    }

    if (!res) return false;
  }
  return true;
}

function matchType(src, dst) {
  const type = typeof src;
  if (type === "string") {
    return String(dst);
  } else if (type === "number") {
    if (+dst === parseFloat(dst)) return +dst;
    else if (!isNaN(Date.parse(dst))) return Date.parse(dst);
  } else if (type === "boolean") {
    const v = String(dst)
      .trim()
      .toLowerCase();
    if (v === "true" || v === "1") return true;
    else if (v === "false" || v === "0") return false;
  }
  return dst;
}

function testFilter(obj, filter) {
  for (const [k, v] of Object.entries(filter)) {
    const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
    const val = matchType(obj[param], v);
    switch (op) {
      case "=":
      case undefined:
        if (obj[param] !== val) return false;
        break;
      case ">":
        if (!(obj[param] > val)) return false;
        break;
      case ">=":
        if (!(obj[param] >= val)) return false;
        break;
      case "<":
        if (!(obj[param] < val)) return false;
        break;
      case "<=":
        if (!(obj[param] <= val)) return false;
        break;
      case "!":
        if (obj[param] === val) return false;
        break;
      default:
        throw new Error(`Unrecognized operator ${op}`);
    }
  }
  return true;
}

function convertMongoQueryToFilters(query, filters) {
  filters = filters || {};
  for (let [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      if (k === "$and")
        for (const vv of v) convertMongoQueryToFilters(vv, filters);
      else throw new Error(`Operator ${k} not supported`);
    } else if (k === "_tags") {
      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (let [kk, vv] of Object.entries(v)) {
          vv = vv.replace(/[^a-zA-Z0-9-]+/g, "_");
          if (kk === "$ne") filters[`Tags.${vv}!`] = true;
          else throw new Error(`Operator ${kk} not supported`);
        }
      } else {
        v = v.replace(/[^a-zA-Z0-9-]+/g, "_");
        filters[`Tags.${v}`] = true;
      }
    } else {
      switch (k) {
        case "_id":
          k = "DeviceID.ID";
          break;
        case "_deviceId._Manufacturer":
          k = "DeviceID.Manufacturer";
          break;
        case "_deviceId._OUI":
          k = "DeviceID.OUI";
          break;
        case "_deviceId._ProductClass":
          k = "DeviceID.ProductClass";
          break;
        case "_deviceId._SerialNumber":
          k = "DeviceID.SerialNumber";
          break;
        case "_lastInform":
          k = "Events.Inform";
          break;
        case "_lastBootstrap":
          k = "Events.0_BOOTSTRAP";
          break;
        case "_lastBoot":
          k = "Events.1_BOOT";
          break;
        case "_registered":
          k = "Events.Registered";
      }

      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (const [kk, vv] of Object.entries(v)) {
          switch (kk) {
            case "$eq":
              filters[k] = vv;
              break;
            case "$ne":
              filters[`${k}!`] = vv;
              break;
            case "$lt":
              filters[`${k}<`] = vv;
              break;
            case "$lte":
              filters[`${k}<=`] = vv;
              break;
            case "$gt":
              filters[`${k}>`] = vv;
              break;
            case "$gte":
              filters[`${k}>=`] = vv;
              break;
            default:
              throw new Error(`Oprator ${kk} not supported`);
          }
        }
      } else {
        filters[k] = v;
      }
    }
  }

  return filters;
}

function sanitizeQueryTypes(query, types) {
  for (const [k, v] of Object.entries(query)) {
    if (k[0] === "$") {
      // Logical operator
      for (const vv of v) sanitizeQueryTypes(vv, types);
    } else if (k in types) {
      if (common.typeOf(v) === common.OBJECT_TYPE) {
        for (const [kk, vv] of Object.entries(v)) {
          switch (kk) {
            case "$in":
            case "$nin":
              for (let i = 0; i < vv.length; ++i) vv[i] = types[k](vv[i]);
              break;
            case "$eq":
            case "$gt":
            case "$gte":
            case "$lt":
            case "$lte":
            case "$ne":
              v[kk] = types[k](vv);
              break;
            case "$exists":
            case "$type":
              // Ignore
              break;
            default:
              throw new Error("Operator not supported");
          }
        }
      } else {
        query[k] = types[k](query[k]);
      }
    }
  }

  return query;
}

exports.expand = expand;
exports.test = test;
exports.sanitizeQueryTypes = sanitizeQueryTypes;
exports.convertMongoQueryToFilters = convertMongoQueryToFilters;
exports.testFilter = testFilter;
<|endoftext|>


<commit_before>
suite("BaseModel", function() {
  test("should define attributes for model", function() {
    var m = new MyModel();

    assert(m.get('id') == null);
    assert(m.get('description') == null);
    assert(m.get('title') == "default title");

    assert(m.attributes.length == 3);

    m.set("id", 10);
    m.set("title", "new title");
    m.set("description", "text");

    assert(m.get('id') == 10);
    assert(m.get('description') == "text");
    assert(m.get('title') == "new title");

    var om = new MyModel({"id": 10});

    assert(om.get('id') == 10);
    assert(om.get('description') == null);
    assert(om.get('title') == "default title");
  });

  test("validate", function() {
    var m = new ModelwithValidators();
    m.validate();

    assert(m.valid() == false);
    assert(Object.keys(m.errors).length == 2);

    assert(m.errors['id'].length == 3);
    m.set("id", "abc");
    m.validate();
    assert(m.errors['id'].length == 2);
    m.set("id", 100);
    m.validate();
    assert(m.errors['id'].length == 1);
    m.set("id", 3);
    m.validate();

    assert(Object.keys(m.errors).length == 1);

    assert(m.errors['title'].length == 2);
    m.set("title", "title123");
    m.validate();
    assert(m.errors['title'].length == 2);
    m.set("title", "title");
    m.validate();
    assert(m.errors['title'].length == 1);
    m.set("title", "New");
    m.validate();
    assert(Object.keys(m.errors).length == 0);
  });

  test("#to_json", function() {
    var m = new MyModel();

    m.set("id", 10);
    m.set("description", "text");

    var j0 = m.to_json();
    var result = JSON.stringify({"id": 10, "title": "default title", "description": "text"});
    assert(j0 == result);

    var j1 = m.to_json(true);
    var result = JSON.stringify({"my-model": JSON.parse(result)});
    assert(j1 == result);
  });

  test("#to_html", function() {
    SiriusApplication.adapter = new JQueryAdapter();
    var m = new MyModel({"id": 10, "title": "my title", "description": "text..."});
    var r = m.to_html();
    assert($(r[0]).prop('tagName') == "B");
    assert($(r[0]).text() == "10");
    assert($(r[0]).attr('class') == 'my-model-id');

    assert($(r[1]).prop('tagName') == "SPAN");
    assert($(r[1]).text() == "my title");
    assert($(r[1]).attr('class') == "my-model-title");

    assert($(r[2]).prop('tagName') == "DIV");
    assert($(r[2]).text() == "text...");
    assert($(r[2]).attr('class') == "description");
  });

  test("#from_json", function() {
    var j = JSON.stringify({"id": 10, "description": "text"});

    var m = MyModel.from_json(j);

    assert(m.get("id") == 10);
    assert(m.get("title") == "default title");
    assert(m.get("description") == "text");
  });

  test("#from_html", function() {
    SiriusApplication.adapter = new JQueryAdapter();
    var m = MyModel.from_html("#my-model-form");

    assert(m.get("id") == 1);
    assert(m.get("title") == "new title");
    assert(m.get("description") == "text...");
  });

});<commit_msg>to_json test<commit_after>
suite("BaseModel", function() {
  test("should define attributes for model", function() {
    var m = new MyModel();

    assert(m.get('id') == null);
    assert(m.get('description') == null);
    assert(m.get('title') == "default title");

    assert(m.attributes.length == 3);

    m.set("id", 10);
    m.set("title", "new title");
    m.set("description", "text");

    assert(m.get('id') == 10);
    assert(m.get('description') == "text");
    assert(m.get('title') == "new title");

    var om = new MyModel({"id": 10});

    assert(om.get('id') == 10);
    assert(om.get('description') == null);
    assert(om.get('title') == "default title");
  });

  test("validate", function() {
    var m = new ModelwithValidators();
    m.validate();

    assert(m.valid() == false);
    assert(Object.keys(m.errors).length == 2);

    assert(m.errors['id'].length == 3);
    m.set("id", "abc");
    m.validate();
    assert(m.errors['id'].length == 2);
    m.set("id", 100);
    m.validate();
    assert(m.errors['id'].length == 1);
    m.set("id", 3);
    m.validate();

    assert(Object.keys(m.errors).length == 1);

    assert(m.errors['title'].length == 2);
    m.set("title", "title123");
    m.validate();
    assert(m.errors['title'].length == 2);
    m.set("title", "title");
    m.validate();
    assert(m.errors['title'].length == 1);
    m.set("title", "New");
    m.validate();
    assert(Object.keys(m.errors).length == 0);
  });

  test("#to_json", function() {
    var m = new MyModel();

    m.set("id", 10);
    m.set("description", "text");

    var j0 = m.to_json();
    var result = JSON.stringify({"id": 10, "title": "default title", "description": "text"});
    assert(j0 == result);

    var j1 = m.to_json(true);
    var result = JSON.stringify({"my_model": JSON.parse(result)});
    assert(j1 == result);
  });

  test("#to_html", function() {
    SiriusApplication.adapter = new JQueryAdapter();
    var m = new MyModel({"id": 10, "title": "my title", "description": "text..."});
    var r = m.to_html();

    assert($(r[0]).prop('tagName') == "B");
    assert($(r[0]).text() == "10");
    assert($(r[0]).attr('class') == 'my-model-id');

    assert($(r[1]).prop('tagName') == "SPAN");
    assert($(r[1]).text() == "my title");
    assert($(r[1]).attr('class') == "my-model-title");

    assert($(r[2]).prop('tagName') == "DIV");
    assert($(r[2]).text() == "text...");
    assert($(r[2]).attr('class') == "description");
  });

  test("#from_json", function() {
    var j = JSON.stringify({"id": 10, "description": "text"});

    var m = MyModel.from_json(j);

    assert(m.get("id") == 10);
    assert(m.get("title") == "default title");
    assert(m.get("description") == "text");
  });

  test("#from_html", function() {
    SiriusApplication.adapter = new JQueryAdapter();
    var m = MyModel.from_html("#my_model_form");

    assert(m.get("id") == 1);
    assert(m.get("title") == "new title");
    assert(m.get("description") == "text...");
  });

  suite("relations", function() {
    var p0 = new Person({id: 1}),
        p1 = new Person({id: 2}),
        n0 = new Name({name: "abc"}),
        n1 = new Name({name: "qwe"}),
        g0 = new Group({name: "group-0"}),
        g1 = new Group({name: "group-1"}),
        g2 = new Group({name: "group-2"});

        p0.add_group(g0);
        p0.add_group(g1);
        p1.add_group(g2);
        p0.add_name(n0);

    test("#has_*", function() {
      assert(p0.attributes.length == 3);
      assert(p0.get("group").length == 2);
      assert(p1.get("group").length == 1);

      //check feedback
      assert(g0.attributes.length == 2);

      assert(g0.get('person_id') == 1);
      assert(g1.get('person_id') == 1);
      assert(g2.get('person_id') == 2);

      assert.throw(function() { p0.add_name(n1);});
    });

    test("#to_json&html", function() {
      SiriusApplication.adapter = new JQueryAdapter();

      var c = function(m){console.log(m);}
      var z = {"id":1,"group":[{"name":"group-0","person_id":1},{"name":"group-1","person_id":1}],"name":{"name":"abc","person_id":1}};

      var j = JSON.parse(p0.to_json());

      assert(z["id"] == j["id"]);
      assert(z["name"]["person_id"] == j["name"]["person_id"]);
      assert(z["name"]["name"] == j["name"]["name"]);
      assert(j["group"].length == 2);
      assert(z["group"][0]["name"] == j["group"][0]["name"]);

      //$("body").append(h)
    });
    test("#from_json, from_html");
  });

});<|endoftext|>


<commit_before>var util = require('util'),
    microseconds = require("../deps/node-microseconds/lib/node-microseconds"),
    uuid = require("../deps/uuidjs/uuid");

/**
 * Queue:
 *
 * Implementation of a queue.
 */
var Queue = function(name, options, callback) {
  options = options || {};
  this.options = {
    maxconnections: options.maxconnections ? options.maxconnections : 5,
    locktimeout: options.locktimeout ? options.locktimeout : 60,
    defaultpriority: options.defaultpriority ? options.defaultpriority : 2,
    maxlockcount: options.maxlockcount ? options.maxlockcount : 2
  };

  this.name = name;

  // stats
  this.enqueued = 0;
  this.dequeued = 0;
  this.deleted = 0;

  // storage
  this.queues = {
    1: [], // high priority
    2: [], // normal priority
    3: []  // low priority
  };
  this.messages = {};
  this.lockedMessages = [];
  this.deadLetters = [];
  this.consumers = [];

  callback(this);
};

Queue.prototype = {

  /**
   * Public API
   */
  publish: function(message, priority, callback) {
    priority = !isNaN(parseInt(priority,10)) ? parseInt(priority,10) : this.options.defaultpriority;
    priority = Math.min(3, Math.max(1, priority));
    var queue = this.queues[priority];
    var envelope = {
      id: this.nextId(),
      message: message,
      priority: priority,
      lock: null,
      added: microseconds.milliseconds(),
      locked: null,
      lockCount: 0
    };
    queue.push(envelope);
    this.messages[envelope.id] = envelope;
    callback(200, envelope);
    this.enqueued ++;
    this.checkConsumers();
  },
  consume: function(id, options, callback) {
    options = options || {};
    var block = options.block != undefined ? options.block : true;
    var lock = options.lock != undefined ? options.lock : true;
    var message;
    if (id) {
      block = false;
      message = this.getMessage(lock);
    } else {
      message = this.getNextMessage(lock);
    }
    if (!message) {
      if (block) {
        this.consumers.push({
          lock: lock,
          callback: callback
        });
      } else {
        callback(204, '');
      }
    } else {
      this.dequeued ++;
      callback(200, message);
    }
  },
  list: function(callback) {
    var obj = {
      messages: this.messages.length,
      queues: this.queues,
      locked: [],
      deadLetters: this.deadLetters
    };
    for (var i=0; i<this.lockedMessages.length; i++) {
      var locked = this.lockedMessages[i];
      obj.locked.push({
        id: locked.id,
        message: locked.message,
        priority: locked.priority,
        added: locked.added,
        locked: locked.locked,
        lockCount: locked.lockCount
      })
    }
    callback(200, obj);
  },
  stats: function(callback) {
    callback(200, {
      messages: this.messages.length,
      consumers: this.consumers.length,
      enqueued: this.enqueued,
      dequeued: this.dequeued,
      deleted: this.deleted,
      deadLetters: this.deadLetters.length
    });
  },
  configure: function(options,callback) {
    if (options) {
      for (option in options) {
        if (this.options[option]) {
          this.options[option] = options[option];
        }
      }
    }
    callback(200, {
      configuration: this.options
    });
  },
  flush: function(callback) {
    this.queues[1] = [];
    this.queues[2] = [];
    this.queues[3] = [];
    this.lockedMessages = [];
    this.messages = {};
    // disconnect waiting consumers gracefully with a 204 header (no content)
    for (var i=0; i<this.consumers.length; i++) {
      this.consumers[i](204,null);
    }
    callback(200, {});
  },
  clearDeadLetters: function(callback) {
    this.deadLetters = [];
    callback(200,{});
  },
  deleteMessage: function(id, lock, callback) {
    var message,
        queue;
    if (this.messages[id]) {
      message = this.messages[id];
      if (!message.lock || (message.lock && message.lock == lock)) {
        // go ahead and delete the message
        if (message.timeout) {
          clearTimeout(message.timeout);
        }
        this.removeMessageFrom(message, this.queues[message.priority]);
        if (message.lock) {
          this.removeMessageFrom(message, this.lockedMessages);
        }
        this.removeMessageFrom(message, this.messages);
        this.deleted ++;
        callback(200, {'success':'Message was deleted'});
      } else {
        callback(403, {'error':'Invalid access.'});
      }
    } else {
      callback(404, {'error': 'Message not found.'});
    }
  },

  removeConsumer: function(consumer) {
    for (var i=0; i<this.consumers.length; i++) {
      if (this.consumers[i].callback == consumer) {
        this.consumers.splice(i,1);
      }
    }
  },

/**
 * Private API
 */

  lockMessage: function(message) {
    message.lock = this.name + '-' + uuid.generate();
    message.locked = microseconds.milliseconds();

    // when locking, set a timeout to clear the lock
    // the timeout has to be cleared in deleteMessage
    var self = this;
    message.timeout = setTimeout(function() {
      util.log('lock timeout on message');
      delete message['timeout'];
      message.locked = null;
      message.lock = null;
      message.lockCount++;
      self.removeMessageFrom(message, self.lockedMessages);
      if (message.lockCount > self.options.maxlockcount) {
        self.removeMessageFrom(message, self.messages);
        self.deadLetters.push(message);
      } else {
        self.queues[message.priority].push(message);
      }
    }, this.options.locktimeout * 1000);
    this.removeMessageFrom(message, this.queues[message.priority]);
    this.lockedMessages.push(message);
  },
  nextId: function() {
    return this.name + '-' + uuid.generate();
  },
  getMessage: function(id, withLock) {
    var message = null;
    if (this.messages[id]) {
      if (this.messages[id].lock && !withLock) {
        return message;
      }
      message = this.messages[id];
      if (withLock) {
        this.lockMessage(message);
      }
    }
    return message;
  },
  getNextMessage: function(withLock) {
    var message, queue;
    for (var i=1; i<=3; i++) {
      queue = this.queues[i];
      for (var j=0; j<queue.length; j++) {
        if (!queue[j].lock) {
          message = queue[j];
          if (withLock) {
            this.lockMessage(message);
          }
          return message;
        }
      }
    }
    return null;
  },
  checkConsumers: function() {
    if (this.consumers.length) {
      var consumer = this.consumers.shift();
      var message = this.getNextMessage(consumer.lock);
      if (message) {
        this.dequeued ++;
        consumer.callback(200, message);
        this.checkConsumers();
      } else {
        this.consumers.push(consumer);
      }
    }
  },
  removeMessageFrom: function(message, queue) {
    if (queue instanceof Array) {
      for (var i=0; i<queue.length; i++) {
        if (queue[i].id == message.id) {
          queue.splice(i, 1);
          return true;
        }
      }
    } else if (queue instanceof Object) {
      if (queue[message.id]) {
        return delete queue[message.id];
      }
    }
    return false;
  }
};

exports.Queue = Queue;<commit_msg>add useful stats on messages and locked messages<commit_after>var util = require('util'),
    microseconds = require("../deps/node-microseconds/lib/node-microseconds"),
    uuid = require("../deps/uuidjs/uuid");

/**
 * Queue:
 *
 * Implementation of a queue.
 */
var Queue = function(name, options, callback) {
  options = options || {};
  this.options = {
    maxconnections: options.maxconnections ? options.maxconnections : 5,
    locktimeout: options.locktimeout ? options.locktimeout : 60,
    defaultpriority: options.defaultpriority ? options.defaultpriority : 2,
    maxlockcount: options.maxlockcount ? options.maxlockcount : 2
  };

  this.name = name;

  // stats
  this.enqueued = 0;
  this.dequeued = 0;
  this.deleted = 0;

  // storage
  this.queues = {
    1: [], // high priority
    2: [], // normal priority
    3: []  // low priority
  };
  this.messages = {};
  this.lockedMessages = [];
  this.deadLetters = [];
  this.consumers = [];

  callback(this);
};

Queue.prototype = {

  /**
   * Public API
   */
  publish: function(message, priority, callback) {
    priority = !isNaN(parseInt(priority,10)) ? parseInt(priority,10) : this.options.defaultpriority;
    priority = Math.min(3, Math.max(1, priority));
    var queue = this.queues[priority];
    var envelope = {
      id: this.nextId(),
      message: message,
      priority: priority,
      lock: null,
      added: microseconds.milliseconds(),
      locked: null,
      lockCount: 0
    };
    queue.push(envelope);
    this.messages[envelope.id] = envelope;
    callback(200, envelope);
    this.enqueued ++;
    this.checkConsumers();
  },
  consume: function(id, options, callback) {
    options = options || {};
    var block = options.block != undefined ? options.block : true;
    var lock = options.lock != undefined ? options.lock : true;
    var message;
    if (id) {
      block = false;
      message = this.getMessage(lock);
    } else {
      message = this.getNextMessage(lock);
    }
    if (!message) {
      if (block) {
        this.consumers.push({
          lock: lock,
          callback: callback
        });
      } else {
        callback(204, '');
      }
    } else {
      this.dequeued ++;
      callback(200, message);
    }
  },
  list: function(callback) {
    var obj = {
      messages: this.messages.length,
      queues: this.queues,
      locked: [],
      deadLetters: this.deadLetters
    };
    for (var i=0; i<this.lockedMessages.length; i++) {
      var locked = this.lockedMessages[i];
      obj.locked.push({
        id: locked.id,
        message: locked.message,
        priority: locked.priority,
        added: locked.added,
        locked: locked.locked,
        lockCount: locked.lockCount
      })
    }
    callback(200, obj);
  },
  stats: function(callback) {
    callback(200, {
      consumers: this.consumers.length,
      messages: this.queues[1].length + this.queues[2].length + this.queues[3].length,
      lockedMessages: this.lockedMessages.length,
      enqueued: this.enqueued,
      dequeued: this.dequeued,
      deleted: this.deleted,
      deadLetters: this.deadLetters.length
    });
  },
  configure: function(options,callback) {
    if (options) {
      for (option in options) {
        if (this.options[option]) {
          this.options[option] = options[option];
        }
      }
    }
    callback(200, {
      configuration: this.options
    });
  },
  flush: function(callback) {
    this.queues[1] = [];
    this.queues[2] = [];
    this.queues[3] = [];
    this.lockedMessages = [];
    this.messages = {};
    // disconnect waiting consumers gracefully with a 204 header (no content)
    for (var i=0; i<this.consumers.length; i++) {
      this.consumers[i](204,null);
    }
    callback(200, {});
  },
  clearDeadLetters: function(callback) {
    this.deadLetters = [];
    callback(200,{});
  },
  deleteMessage: function(id, lock, callback) {
    var message,
        queue;
    if (this.messages[id]) {
      message = this.messages[id];
      if (!message.lock || (message.lock && message.lock == lock)) {
        // go ahead and delete the message
        if (message.timeout) {
          clearTimeout(message.timeout);
        }
        this.removeMessageFrom(message, this.queues[message.priority]);
        if (message.lock) {
          this.removeMessageFrom(message, this.lockedMessages);
        }
        this.removeMessageFrom(message, this.messages);
        this.deleted ++;
        callback(200, {'success':'Message was deleted'});
      } else {
        callback(403, {'error':'Invalid access.'});
      }
    } else {
      callback(404, {'error': 'Message not found.'});
    }
  },

  removeConsumer: function(consumer) {
    for (var i=0; i<this.consumers.length; i++) {
      if (this.consumers[i].callback == consumer) {
        util.log('removing dead consumer');
        this.consumers.splice(i,1);
      }
    }
  },

/**
 * Private API
 */

  lockMessage: function(message) {
    message.lock = this.name + '-' + uuid.generate();
    message.locked = microseconds.milliseconds();

    // when locking, set a timeout to clear the lock
    // the timeout has to be cleared in deleteMessage
    var self = this;
    message.timeout = setTimeout(function() {
      util.log('lock timeout on message');
      delete message['timeout'];
      message.locked = null;
      message.lock = null;
      message.lockCount++;
      self.removeMessageFrom(message, self.lockedMessages);
      if (message.lockCount > self.options.maxlockcount) {
        self.removeMessageFrom(message, self.messages);
        self.deadLetters.push(message);
      } else {
        self.queues[message.priority].push(message);
      }
    }, this.options.locktimeout * 1000);
    this.removeMessageFrom(message, this.queues[message.priority]);
    this.lockedMessages.push(message);
  },
  nextId: function() {
    return this.name + '-' + uuid.generate();
  },
  getMessage: function(id, withLock) {
    var message = null;
    if (this.messages[id]) {
      if (this.messages[id].lock && !withLock) {
        return message;
      }
      message = this.messages[id];
      if (withLock) {
        this.lockMessage(message);
      }
    }
    return message;
  },
  getNextMessage: function(withLock) {
    var message, queue;
    for (var i=1; i<=3; i++) {
      queue = this.queues[i];
      for (var j=0; j<queue.length; j++) {
        if (!queue[j].lock) {
          message = queue[j];
          if (withLock) {
            this.lockMessage(message);
          }
          return message;
        }
      }
    }
    return null;
  },
  checkConsumers: function() {
    if (this.consumers.length) {
      var consumer = this.consumers.shift();
      var message = this.getNextMessage(consumer.lock);
      if (message) {
        this.dequeued ++;
        consumer.callback(200, message);
        this.checkConsumers();
      } else {
        this.consumers.push(consumer);
      }
    }
  },
  removeMessageFrom: function(message, queue) {
    if (queue instanceof Array) {
      for (var i=0; i<queue.length; i++) {
        if (queue[i].id == message.id) {
          queue.splice(i, 1);
          return true;
        }
      }
    } else if (queue instanceof Object) {
      if (queue[message.id]) {
        return delete queue[message.id];
      }
    }
    return false;
  }
};

exports.Queue = Queue;<|endoftext|>