Index: frontend/node_modules/jsonpath/lib/aesprim.js
===================================================================
--- frontend/node_modules/jsonpath/lib/aesprim.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/aesprim.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,18 @@
+var fs = require('fs');
+var Module = require('module');
+
+var file = require.resolve('esprima');
+var source = fs.readFileSync(file, 'utf-8');
+
+// inject '@' as a valid identifier!
+source = source.replace(/(function isIdentifierStart\(ch\) {\s+return)/m, '$1 (ch == 0x40) || ');
+
+//If run as script just output patched file
+if (require.main === module)
+  console.log(source);
+else {
+  var _module = new Module('aesprim');
+  _module._compile(source, __filename);
+
+  module.exports = _module.exports;
+}
Index: frontend/node_modules/jsonpath/lib/dict.js
===================================================================
--- frontend/node_modules/jsonpath/lib/dict.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/dict.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,6 @@
+module.exports = {
+  identifier: "[a-zA-Z_]+[a-zA-Z0-9_]*",
+  integer: "-?(?:0|[1-9][0-9]*)",
+  qq_string: "\"(?:\\\\[\"bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\"\\\\])*\"",
+  q_string: "'(?:\\\\[\'bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\'\\\\])*'"
+};
Index: frontend/node_modules/jsonpath/lib/grammar.js
===================================================================
--- frontend/node_modules/jsonpath/lib/grammar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/grammar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,106 @@
+var dict = require('./dict');
+var fs = require('fs');
+var grammar = {
+
+    lex: {
+
+        macros: {
+            esc: "\\\\",
+            int: dict.integer
+        },
+
+        rules: [
+            ["\\$", "return 'DOLLAR'"],
+            ["\\.\\.", "return 'DOT_DOT'"],
+            ["\\.", "return 'DOT'"],
+            ["\\*", "return 'STAR'"],
+            [dict.identifier, "return 'IDENTIFIER'"],
+            ["\\[", "return '['"],
+            ["\\]", "return ']'"],
+            [",", "return ','"],
+            ["({int})?\\:({int})?(\\:({int})?)?", "return 'ARRAY_SLICE'"],
+            ["{int}", "return 'INTEGER'"],
+            [dict.qq_string, "yytext = yytext.substr(1,yyleng-2); return 'QQ_STRING';"],
+            [dict.q_string, "yytext = yytext.substr(1,yyleng-2); return 'Q_STRING';"],
+            ["\\(.+?\\)(?=\\])", "return 'SCRIPT_EXPRESSION'"],
+            ["\\?\\(.+?\\)(?=\\])", "return 'FILTER_EXPRESSION'"]
+        ]
+    },
+
+    start: "JSON_PATH",
+
+    bnf: {
+
+        JSON_PATH: [
+                [ 'DOLLAR',                 'yy.ast.set({ expression: { type: "root", value: $1 } }); yy.ast.unshift(); return yy.ast.yield()' ],
+                [ 'DOLLAR PATH_COMPONENTS', 'yy.ast.set({ expression: { type: "root", value: $1 } }); yy.ast.unshift(); return yy.ast.yield()' ],
+                [ 'LEADING_CHILD_MEMBER_EXPRESSION',                 'yy.ast.unshift(); return yy.ast.yield()' ],
+                [ 'LEADING_CHILD_MEMBER_EXPRESSION PATH_COMPONENTS', 'yy.ast.set({ operation: "member", scope: "child", expression: { type: "identifier", value: $1 }}); yy.ast.unshift(); return yy.ast.yield()' ] ],
+
+        PATH_COMPONENTS: [
+                [ 'PATH_COMPONENT',                 '' ],
+                [ 'PATH_COMPONENTS PATH_COMPONENT', '' ] ],
+
+        PATH_COMPONENT: [
+                [ 'MEMBER_COMPONENT',    'yy.ast.set({ operation: "member" }); yy.ast.push()' ],
+                [ 'SUBSCRIPT_COMPONENT', 'yy.ast.set({ operation: "subscript" }); yy.ast.push() ' ] ],
+
+        MEMBER_COMPONENT: [
+                [ 'CHILD_MEMBER_COMPONENT',      'yy.ast.set({ scope: "child" })' ],
+                [ 'DESCENDANT_MEMBER_COMPONENT', 'yy.ast.set({ scope: "descendant" })' ] ],
+
+        CHILD_MEMBER_COMPONENT: [
+                [ 'DOT MEMBER_EXPRESSION', '' ] ],
+
+        LEADING_CHILD_MEMBER_EXPRESSION: [
+                [ 'MEMBER_EXPRESSION', 'yy.ast.set({ scope: "child", operation: "member" })' ] ],
+
+        DESCENDANT_MEMBER_COMPONENT: [
+                [ 'DOT_DOT MEMBER_EXPRESSION', '' ] ],
+
+        MEMBER_EXPRESSION: [
+                [ 'STAR',              'yy.ast.set({ expression: { type: "wildcard", value: $1 } })' ],
+                [ 'IDENTIFIER',        'yy.ast.set({ expression: { type: "identifier", value: $1 } })' ],
+                [ 'SCRIPT_EXPRESSION', 'yy.ast.set({ expression: { type: "script_expression", value: $1 } })' ],
+                [ 'INTEGER',           'yy.ast.set({ expression: { type: "numeric_literal", value: parseInt($1) } })' ],
+                [ 'END',               '' ] ],
+
+        SUBSCRIPT_COMPONENT: [
+                [ 'CHILD_SUBSCRIPT_COMPONENT',      'yy.ast.set({ scope: "child" })' ],
+                [ 'DESCENDANT_SUBSCRIPT_COMPONENT', 'yy.ast.set({ scope: "descendant" })' ] ],
+
+        CHILD_SUBSCRIPT_COMPONENT: [
+                [ '[ SUBSCRIPT ]', '' ] ],
+
+        DESCENDANT_SUBSCRIPT_COMPONENT: [
+                [ 'DOT_DOT [ SUBSCRIPT ]', '' ] ],
+
+        SUBSCRIPT: [
+                [ 'SUBSCRIPT_EXPRESSION', '' ],
+                [ 'SUBSCRIPT_EXPRESSION_LIST', '$1.length > 1? yy.ast.set({ expression: { type: "union", value: $1 } }) : $$ = $1' ] ],
+
+        SUBSCRIPT_EXPRESSION_LIST: [
+                [ 'SUBSCRIPT_EXPRESSION_LISTABLE', '$$ = [$1]'],
+                [ 'SUBSCRIPT_EXPRESSION_LIST , SUBSCRIPT_EXPRESSION_LISTABLE', '$$ = $1.concat($3)' ] ],
+
+        SUBSCRIPT_EXPRESSION_LISTABLE: [
+                [ 'INTEGER',           '$$ = { expression: { type: "numeric_literal", value: parseInt($1) } }; yy.ast.set($$)' ],
+                [ 'STRING_LITERAL',    '$$ = { expression: { type: "string_literal", value: $1 } }; yy.ast.set($$)' ],
+                [ 'ARRAY_SLICE',       '$$ = { expression: { type: "slice", value: $1 } }; yy.ast.set($$)' ] ],
+
+        SUBSCRIPT_EXPRESSION: [
+                [ 'STAR',              '$$ = { expression: { type: "wildcard", value: $1 } }; yy.ast.set($$)' ],
+                [ 'SCRIPT_EXPRESSION', '$$ = { expression: { type: "script_expression", value: $1 } }; yy.ast.set($$)' ],
+                [ 'FILTER_EXPRESSION', '$$ = { expression: { type: "filter_expression", value: $1 } }; yy.ast.set($$)' ] ],
+
+        STRING_LITERAL: [
+                [ 'QQ_STRING', "$$ = $1" ],
+                [ 'Q_STRING',  "$$ = $1" ] ]
+    }
+};
+if (fs.readFileSync) {
+  grammar.moduleInclude = fs.readFileSync(require.resolve("../include/module.js"));
+  grammar.actionInclude = fs.readFileSync(require.resolve("../include/action.js"));
+}
+
+module.exports = grammar;
Index: frontend/node_modules/jsonpath/lib/handlers.js
===================================================================
--- frontend/node_modules/jsonpath/lib/handlers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/handlers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,409 @@
+var aesprim = require('./aesprim');
+var slice = require('./slice');
+var _evaluate = require('static-eval');
+var _uniq = require('underscore').uniq;
+
+// Property names that must never be accessible in expressions.
+// Mitigates prototype pollution and constructor escape attacks.
+var UNSAFE_PROPERTY_NAMES = Object.create(null);
+
+/* jshint -W069: true */
+UNSAFE_PROPERTY_NAMES['constructor'] = true;
+UNSAFE_PROPERTY_NAMES['__proto__'] = true;
+UNSAFE_PROPERTY_NAMES['prototype'] = true;
+/* jshint -W069: false */
+
+function isUnsafePropertyName(name) {
+  return typeof name === 'string' && UNSAFE_PROPERTY_NAMES[name] === true;
+}
+
+function isSafeAst(ast) {
+  if (!ast || typeof ast !== 'object') return false;
+
+  function walk(node) {
+    if (!node || typeof node !== 'object' || !node.type) {
+      return false;
+    }
+
+    switch (node.type) {
+
+      // ===== SAFE TERMINALS =====
+
+      case 'Literal':
+        return true;
+
+      case 'Identifier':
+        // Only allow the special scope identifier
+        return node.name === '@';
+
+
+      // ===== PROPERTY ACCESS =====
+
+      case 'MemberExpression': {
+        if (!walk(node.object)) {
+          return false;
+        }
+
+        // Non-computed: obj.property
+        if (!node.computed && node.property.type === 'Identifier') {
+          if (isUnsafePropertyName(node.property.name)) {
+            return false;
+          }
+          return true;
+        }
+
+        // Computed: obj["property"]
+        if (node.computed) {
+          if (!walk(node.property)) {
+            return false;
+          }
+
+          if (
+            node.property.type === 'Literal' &&
+            isUnsafePropertyName(String(node.property.value))
+          ) {
+            return false;
+          }
+
+          return true;
+        }
+
+        return false;
+      }
+
+
+      // ===== EXPRESSIONS =====
+
+      case 'UnaryExpression':
+        return walk(node.argument);
+
+      case 'BinaryExpression':
+      case 'LogicalExpression':
+        return walk(node.left) && walk(node.right);
+
+      case 'ConditionalExpression':
+        return (
+          walk(node.test) &&
+          walk(node.consequent) &&
+          walk(node.alternate)
+        );
+
+      case 'ArrayExpression':
+        for (var i = 0; i < node.elements.length; i++) {
+          if (!walk(node.elements[i])) {
+            return false;
+          }
+        }
+        return true;
+
+      case 'ObjectExpression':
+        for (var j = 0; j < node.properties.length; j++) {
+          var prop = node.properties[j];
+
+          // Reject unsafe keys
+          if (
+            prop.key &&
+            (
+              (prop.key.type === 'Identifier' &&
+               isUnsafePropertyName(prop.key.name)) ||
+              (prop.key.type === 'Literal' &&
+               isUnsafePropertyName(String(prop.key.value)))
+            )
+          ) {
+            return false;
+          }
+
+          if (!walk(prop.value)) {
+            return false;
+          }
+        }
+        return true;
+
+
+      // ===== EXPLICITLY REJECT DANGEROUS TYPES =====
+      // Security: do not rely on default deny; list each code-execution / escape vector.
+
+      case 'CallExpression':
+      case 'NewExpression':
+      case 'FunctionExpression':
+      case 'ArrowFunctionExpression':
+      case 'ThisExpression':
+      case 'AssignmentExpression':
+      case 'UpdateExpression':
+      case 'SequenceExpression':
+      case 'TemplateLiteral':
+      case 'TemplateElement':
+      case 'TaggedTemplateExpression':
+      case 'ReturnStatement':
+      case 'ExpressionStatement':
+        return false;
+
+
+      // ===== DEFAULT DENY =====
+
+      default:
+        return false;
+    }
+  }
+
+  return walk(ast);
+}
+
+var Handlers = function() {
+  return this.initialize.apply(this, arguments);
+}
+
+Handlers.prototype.initialize = function() {
+  this.traverse = traverser(true);
+  this.descend = traverser();
+}
+
+Handlers.prototype.keys = Object.keys;
+
+Handlers.prototype.resolve = function(component) {
+
+  var key = [ component.operation, component.scope, component.expression.type ].join('-');
+  var method = this._fns[key];
+
+  if (!method) throw new Error("couldn't resolve key: " + key);
+  return method.bind(this);
+};
+
+Handlers.prototype.register = function(key, handler) {
+
+  if (!handler instanceof Function) {
+    throw new Error("handler must be a function");
+  }
+
+  this._fns[key] = handler;
+};
+
+Handlers.prototype._fns = {
+
+  'member-child-identifier': function(component, partial) {
+    var key = component.expression.value;
+    var value = partial.value;
+    if (value instanceof Object && key in value) {
+      return [ { value: value[key], path: partial.path.concat(key) } ]
+    }
+  },
+
+  'member-descendant-identifier':
+    _traverse(function(key, value, ref) { return key == ref }),
+
+  'subscript-child-numeric_literal':
+    _descend(function(key, value, ref) { return key === ref }),
+
+  'member-child-numeric_literal':
+    _descend(function(key, value, ref) { return String(key) === String(ref) }),
+
+  'subscript-descendant-numeric_literal':
+    _traverse(function(key, value, ref) { return key === ref }),
+
+  'member-child-wildcard':
+    _descend(function() { return true }),
+
+  'member-descendant-wildcard':
+    _traverse(function() { return true }),
+
+  'subscript-descendant-wildcard':
+    _traverse(function() { return true }),
+
+  'subscript-child-wildcard':
+    _descend(function() { return true }),
+
+  'subscript-child-slice': function(component, partial) {
+    if (is_array(partial.value)) {
+      var args = component.expression.value.split(':').map(_parse_nullable_int);
+      var values = partial.value.map(function(v, i) { return { value: v, path: partial.path.concat(i) } });
+      return slice.apply(null, [values].concat(args));
+    }
+  },
+
+  'subscript-child-union': function(component, partial) {
+    var results = [];
+    component.expression.value.forEach(function(component) {
+      var _component = { operation: 'subscript', scope: 'child', expression: component.expression };
+      var handler = this.resolve(_component);
+      var _results = handler(_component, partial);
+      if (_results) {
+        results = results.concat(_results);
+      }
+    }, this);
+
+    return unique(results);
+  },
+
+  'subscript-descendant-union': function(component, partial, count) {
+
+    var jp = require('..');
+    var self = this;
+
+    var results = [];
+    var nodes = jp.nodes(partial, '$..*').slice(1);
+
+    nodes.forEach(function(node) {
+      if (results.length >= count) return;
+      component.expression.value.forEach(function(component) {
+        var _component = { operation: 'subscript', scope: 'child', expression: component.expression };
+        var handler = self.resolve(_component);
+        var _results = handler(_component, node);
+        results = results.concat(_results);
+      });
+    });
+
+    return unique(results);
+  },
+
+  'subscript-child-filter_expression': function(component, partial, count) {
+
+    // slice out the expression from ?(expression)
+    var src = component.expression.value.slice(2, -1);
+    var ast = aesprim.parse(src).body[0].expression;
+
+    var passable = function(key, value) {
+      return evaluate(ast, { '@': value });
+    }
+
+    return this.descend(partial, null, passable, count);
+
+  },
+
+  'subscript-descendant-filter_expression': function(component, partial, count) {
+
+    // slice out the expression from ?(expression)
+    var src = component.expression.value.slice(2, -1);
+    var ast = aesprim.parse(src).body[0].expression;
+
+    var passable = function(key, value) {
+      return evaluate(ast, { '@': value });
+    }
+
+    return this.traverse(partial, null, passable, count);
+  },
+
+  'subscript-child-script_expression': function(component, partial) {
+    var exp = component.expression.value.slice(1, -1);
+    return eval_recurse(partial, exp, '$[{{value}}]');
+  },
+
+  'member-child-script_expression': function(component, partial) {
+    var exp = component.expression.value.slice(1, -1);
+    return eval_recurse(partial, exp, '$.{{value}}');
+  },
+
+  'member-descendant-script_expression': function(component, partial) {
+    var exp = component.expression.value.slice(1, -1);
+    return eval_recurse(partial, exp, '$..value');
+  }
+};
+
+Handlers.prototype._fns['subscript-child-string_literal'] =
+	Handlers.prototype._fns['member-child-identifier'];
+
+Handlers.prototype._fns['member-descendant-numeric_literal'] =
+    Handlers.prototype._fns['subscript-descendant-string_literal'] =
+    Handlers.prototype._fns['member-descendant-identifier'];
+
+function eval_recurse(partial, src, template) {
+
+  var jp = require('./index');
+  var ast = aesprim.parse(src).body[0].expression;
+  var value = evaluate(ast, { '@': partial.value });
+  var path = template.replace(/\{\{\s*value\s*\}\}/g, value);
+
+  var results = jp.nodes(partial.value, path);
+  results.forEach(function(r) {
+    r.path = partial.path.concat(r.path.slice(1));
+  });
+
+  return results;
+}
+
+function is_array(val) {
+  return Array.isArray(val);
+}
+
+function is_object(val) {
+  // is this a non-array, non-null object?
+  return val && !(val instanceof Array) && val instanceof Object;
+}
+
+function traverser(recurse) {
+
+  return function(partial, ref, passable, count) {
+
+    var value = partial.value;
+    var path = partial.path;
+
+    var results = [];
+
+    var descend = function(value, path) {
+
+      if (is_array(value)) {
+        value.forEach(function(element, index) {
+          if (results.length >= count) { return }
+          if (passable(index, element, ref)) {
+            results.push({ path: path.concat(index), value: element });
+          }
+        });
+        value.forEach(function(element, index) {
+          if (results.length >= count) { return }
+          if (recurse) {
+            descend(element, path.concat(index));
+          }
+        });
+      } else if (is_object(value)) {
+        this.keys(value).forEach(function(k) {
+          if (results.length >= count) { return }
+          if (passable(k, value[k], ref)) {
+            results.push({ path: path.concat(k), value: value[k] });
+          }
+        })
+        this.keys(value).forEach(function(k) {
+          if (results.length >= count) { return }
+          if (recurse) {
+            descend(value[k], path.concat(k));
+          }
+        });
+      }
+    }.bind(this);
+    descend(value, path);
+    return results;
+  }
+}
+
+function _descend(passable) {
+  return function(component, partial, count) {
+    return this.descend(partial, component.expression.value, passable, count);
+  }
+}
+
+function _traverse(passable) {
+  return function(component, partial, count) {
+    return this.traverse(partial, component.expression.value, passable, count);
+  }
+}
+
+function evaluate(ast, scope) {
+  if (!isSafeAst(ast)) {
+    throw new Error('Unsafe expression: script and filter expressions may only access the current node (@) with safe property names');
+  }
+  try { return _evaluate(ast, scope) }
+  catch (e) { }
+}
+
+function unique(results) {
+  results = results.filter(function(d) { return d })
+  return _uniq(
+    results,
+    function(r) { return r.path.map(function(c) { return String(c).replace('-', '--') }).join('-') }
+  );
+}
+
+function _parse_nullable_int(val) {
+  var sval = String(val);
+  return sval.match(/^-?[0-9]+$/) ? parseInt(sval) : null;
+}
+
+module.exports = Handlers;
Index: frontend/node_modules/jsonpath/lib/index.js
===================================================================
--- frontend/node_modules/jsonpath/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,304 @@
+var assert = require('assert');
+var dict = require('./dict');
+var Parser = require('./parser');
+var Handlers = require('./handlers');
+
+var JSONPath = function() {
+  this.initialize.apply(this, arguments);
+};
+
+JSONPath.prototype.initialize = function() {
+  this.parser = new Parser();
+  this.handlers = new Handlers();
+};
+
+JSONPath.prototype.parse = function(string) {
+  assert.ok(_is_string(string), "we need a path");
+  return this.parser.parse(string);
+};
+
+JSONPath.prototype.parent = function(obj, string) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(string, "we need a path");
+
+  var node = this.nodes(obj, string)[0];
+  if (node) this._assert_safe_path_keys(node.path);
+  var key = node.path.pop(); /* jshint unused:false */
+  return this.value(obj, node.path);
+}
+
+JSONPath.prototype.apply = function(obj, string, fn) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(string, "we need a path");
+  assert.equal(typeof fn, "function", "fn needs to be function")
+
+  var nodes = this.nodes(obj, string).sort(function(a, b) {
+    // sort nodes so we apply from the bottom up
+    return b.path.length - a.path.length;
+  });
+
+  nodes.forEach(function(node) {
+    this._assert_safe_path_keys(node.path);
+    var key = node.path.pop();
+    var parent = this.value(obj, this.stringify(node.path));
+    var val = node.value = fn.call(obj, parent[key]);
+    parent[key] = val;
+  }, this);
+
+  return nodes;
+}
+
+JSONPath.prototype.value = function(obj, path, value) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(path, "we need a path");
+
+  if (arguments.length >= 3) {
+    var node = this.nodes(obj, path).shift();
+    if (!node) return this._vivify(obj, path, value);
+    this._assert_safe_path_keys(node.path);
+    var key = node.path.slice(-1).shift();
+    var parent = this.parent(obj, this.stringify(node.path));
+    parent[key] = value;
+  }
+  return this.query(obj, this.stringify(path), 1).shift();
+}
+
+JSONPath.prototype._vivify = function(obj, string, value) {
+
+  var self = this;
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(string, "we need a path");
+
+  var path = this.parser.parse(string)
+    .map(function(component) { return component.expression.value });
+
+  this._assert_safe_path_keys(path);
+
+  var setValue = function(path, value) {
+    var key = path.pop();
+    var node = self.value(obj, path);
+    if (!node) {
+      setValue(path.concat(), typeof key === 'string' ? {} : []);
+      node = self.value(obj, path);
+    }
+    self._assert_safe_key(key);
+    node[key] = value;
+  }
+  setValue(path, value);
+  return this.query(obj, string)[0];
+}
+
+JSONPath.prototype.query = function(obj, string, count) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(_is_string(string), "we need a path");
+
+  var results = this.nodes(obj, string, count)
+    .map(function(r) { return r.value });
+
+  return results;
+};
+
+JSONPath.prototype.paths = function(obj, string, count) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(string, "we need a path");
+
+  var results = this.nodes(obj, string, count)
+    .map(function(r) { return r.path });
+
+  return results;
+};
+
+JSONPath.prototype.nodes = function(obj, string, count) {
+
+  assert.ok(obj instanceof Object, "obj needs to be an object");
+  assert.ok(string, "we need a path");
+
+  if (count === 0) return [];
+
+  var path = this.parser.parse(string);
+  this._assert_safe_components(path);
+  var handlers = this.handlers;
+
+  var partials = [ { path: ['$'], value: obj } ];
+  var matches = [];
+
+  if (path.length && path[0].expression.type == 'root') path.shift();
+
+  if (!path.length) return partials;
+
+  path.forEach(function(component, index) {
+
+    if (matches.length >= count) return;
+    var handler = handlers.resolve(component);
+    var _partials = [];
+
+    partials.forEach(function(p) {
+
+      if (matches.length >= count) return;
+      var results = handler(component, p, count);
+
+      if (index == path.length - 1) {
+        // if we're through the components we're done
+        matches = matches.concat(results || []);
+      } else {
+        // otherwise accumulate and carry on through
+        _partials = _partials.concat(results || []);
+      }
+    });
+
+    partials = _partials;
+
+  });
+
+  return count ? matches.slice(0, count) : matches;
+};
+
+JSONPath.prototype.stringify = function(path) {
+
+  assert.ok(path, "we need a path");
+
+  var string = '$';
+
+  var templates = {
+    'descendant-member': '..{{value}}',
+    'child-member': '.{{value}}',
+    'descendant-subscript': '..[{{value}}]',
+    'child-subscript': '[{{value}}]'
+  };
+
+  path = this._normalize(path);
+
+  path.forEach(function(component) {
+
+    if (component.expression.type == 'root') return;
+
+    var key = [component.scope, component.operation].join('-');
+    var template = templates[key];
+    var value;
+
+    if (component.expression.type == 'string_literal') {
+      value = JSON.stringify(component.expression.value)
+    } else {
+      value = component.expression.value;
+    }
+
+    if (!template) throw new Error("couldn't find template " + key);
+
+    string += template.replace(/{{value}}/, value);
+  });
+
+  return string;
+}
+
+JSONPath.prototype._normalize = function(path) {
+
+  assert.ok(path, "we need a path");
+
+  if (typeof path == "string") {
+
+    return this.parser.parse(path);
+
+  } else if (Array.isArray(path) && typeof path[0] == "string") {
+
+    var _path = [ { expression: { type: "root", value: "$" } } ];
+
+    path.forEach(function(component, index) {
+
+      if (component == '$' && index === 0) return;
+
+      if (typeof component == "string" && component.match("^" + dict.identifier + "$")) {
+        this._assert_safe_key(component);
+
+        _path.push({
+          operation: 'member',
+          scope: 'child',
+          expression: { value: component, type: 'identifier' }
+        });
+
+      } else {
+
+        var type = typeof component == "number" ?
+          'numeric_literal' : 'string_literal';
+
+        if (type === 'string_literal') this._assert_safe_key(component);
+
+        _path.push({
+          operation: 'subscript',
+          scope: 'child',
+          expression: { value: component, type: type }
+        });
+      }
+    }, this);
+
+    return _path;
+
+  } else if (Array.isArray(path) && typeof path[0] == "object") {
+
+    return path
+  }
+
+  throw new Error("couldn't understand path " + path);
+}
+
+JSONPath.prototype._assert_safe_key = function(key) {
+  if (_is_unsafe_key(key)) {
+    throw new Error("Unsafe key in JSONPath: " + key);
+  }
+}
+
+JSONPath.prototype._assert_safe_path_keys = function(path) {
+  if (!Array.isArray(path)) return;
+  path.forEach(function(key) {
+    if (key === '$') return;
+    if (typeof key === 'string') this._assert_safe_key(key);
+  }, this);
+}
+
+JSONPath.prototype._assert_safe_components = function(components) {
+  var self = this;
+  if (!Array.isArray(components)) return;
+
+  var checkExpression = function(expression) {
+    if (!expression) return;
+    if (expression.type === 'identifier' || expression.type === 'string_literal') {
+      self._assert_safe_key(expression.value);
+      return;
+    }
+
+    if (expression.type === 'union' && Array.isArray(expression.value)) {
+      expression.value.forEach(function(component) {
+        if (component && component.expression) {
+          checkExpression(component.expression);
+        }
+      });
+    }
+  };
+
+  components.forEach(function(component) {
+    if (component && component.expression) {
+      checkExpression(component.expression);
+    }
+  });
+}
+
+function _is_string(obj) {
+  return Object.prototype.toString.call(obj) == '[object String]';
+}
+
+function _is_unsafe_key(key) {
+  return key === '__proto__' || key === 'prototype' || key === 'constructor';
+}
+
+JSONPath.Handlers = Handlers;
+JSONPath.Parser = Parser;
+
+var instance = new JSONPath;
+instance.JSONPath = JSONPath;
+
+module.exports = instance;
Index: frontend/node_modules/jsonpath/lib/parser.js
===================================================================
--- frontend/node_modules/jsonpath/lib/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,21 @@
+var grammar = require('./grammar');
+var gparser = require('../generated/parser');
+
+var Parser = function() {
+
+  var parser = new gparser.Parser();
+
+  var _parseError = parser.parseError;
+  parser.yy.parseError = function() {
+    if (parser.yy.ast) {
+      parser.yy.ast.initialize();
+    }
+    _parseError.apply(parser, arguments);
+  }
+
+  return parser;
+
+};
+
+Parser.grammar = grammar;
+module.exports = Parser;
Index: frontend/node_modules/jsonpath/lib/slice.js
===================================================================
--- frontend/node_modules/jsonpath/lib/slice.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/lib/slice.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,41 @@
+module.exports = function(arr, start, end, step) {
+
+  if (typeof start == 'string') throw new Error("start cannot be a string");
+  if (typeof end == 'string') throw new Error("end cannot be a string");
+  if (typeof step == 'string') throw new Error("step cannot be a string");
+
+  var len = arr.length;
+
+  if (step === 0) throw new Error("step cannot be zero");
+  step = step ? integer(step) : 1;
+
+  // normalize negative values
+  start = start < 0 ? len + start : start;
+  end = end < 0 ? len + end : end;
+
+  // default extents to extents
+  start = integer(start === 0 ? 0 : !start ? (step > 0 ? 0 : len - 1) : start);
+  end = integer(end === 0 ? 0 : !end ? (step > 0 ? len : -1) : end);
+
+  // clamp extents
+  start = step > 0 ? Math.max(0, start) : Math.min(len, start);
+  end = step > 0 ? Math.min(end, len) : Math.max(-1, end);
+
+  // return empty if extents are backwards
+  if (step > 0 && end <= start) return [];
+  if (step < 0 && start <= end) return [];
+
+  var result = [];
+
+  for (var i = start; i != end; i += step) {
+    if ((step < 0 && i <= end) || (step > 0 && i >= end)) break;
+    result.push(arr[i]);
+  }
+
+  return result;
+}
+
+function integer(val) {
+  return String(val).match(/^[0-9]+$/) ? parseInt(val) :
+    Number.isFinite(val) ? parseInt(val, 10) : 0;
+}
