Index: frontend/node_modules/jsonpath/test/data/store.json
===================================================================
--- frontend/node_modules/jsonpath/test/data/store.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/data/store.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,31 @@
+{ "store": {
+    "book": [ 
+      { "category": "reference",
+        "author": "Nigel Rees",
+        "title": "Sayings of the Century",
+        "price": 8.95
+      },
+      { "category": "fiction",
+        "author": "Evelyn Waugh",
+        "title": "Sword of Honour",
+        "price": 12.99
+      },
+      { "category": "fiction",
+        "author": "Herman Melville",
+        "title": "Moby Dick",
+        "isbn": "0-553-21311-3",
+        "price": 8.99
+      },
+      { "category": "fiction",
+        "author": "J. R. R. Tolkien",
+        "title": "The Lord of the Rings",
+        "isbn": "0-395-19395-8",
+        "price": 22.99
+      }
+    ],
+    "bicycle": {
+      "color": "red",
+      "price": 19.95
+    }
+  }
+}
Index: frontend/node_modules/jsonpath/test/lessons.js
===================================================================
--- frontend/node_modules/jsonpath/test/lessons.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/lessons.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,38 @@
+var assert = require('assert');
+var jp = require('../');
+
+var data = require('./data/store.json');
+
+suite('orig-google-code-issues', function() {
+    
+  test('comma in eval', function() {
+    var pathExpression = '$..book[?(@.price && ",")]'
+    var results = jp.query(data, pathExpression);
+    assert.deepEqual(results, data.store.book);
+  });
+
+  test('member names with dots', function() {
+    var data = { 'www.google.com': 42, 'www.wikipedia.org': 190 };
+    var results = jp.query(data, "$['www.google.com']");
+    assert.deepEqual(results, [ 42 ]);
+  });
+
+  test('nested objects with filter', function() {
+    var data = { dataResult: { object: { objectInfo: { className: "folder", typeName: "Standard Folder", id: "uniqueId" } } } };
+    var results = jp.query(data, "$..object[?(@.className=='folder')]");
+    assert.deepEqual(results, [ data.dataResult.object.objectInfo ]);
+  });
+
+  test('script expressions with @ char', function() {
+    var data = { "DIV": [{ "@class": "value", "val": 5 }] };
+    var results = jp.query(data, "$..DIV[?(@['@class']=='value')]");
+    assert.deepEqual(results, data.DIV);
+  });
+
+  test('negative slices', function() {
+    var results = jp.query(data, "$..book[-1:].title");
+    assert.deepEqual(results, ['The Lord of the Rings']);
+  });
+
+});
+
Index: frontend/node_modules/jsonpath/test/parse.js
===================================================================
--- frontend/node_modules/jsonpath/test/parse.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/parse.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,164 @@
+var assert = require('assert');
+var jp = require('../');
+var util = require('util');
+
+suite('parse', function() {
+
+  test('should parse root-only', function() {
+    var path = jp.parse('$');
+    assert.deepEqual(path, [ { expression: { type: 'root', value: '$' } } ]);
+  });
+
+  test('parse path for store', function() {
+    var path = jp.parse('$.store');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } }
+    ])
+  });
+
+  test('parse path for the authors of all books in the store', function() {
+    var path = jp.parse('$.store.book[*].author');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'wildcard', value: '*' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'author' } }
+    ])
+  });
+
+  test('parse path for all authors', function() {
+    var path = jp.parse('$..author');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'author' } }
+    ])
+  });
+
+  test('parse path for all authors via subscript descendant string literal', function() {
+    var path = jp.parse("$..['author']");
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'subscript', scope: 'descendant', expression: { type: 'string_literal', value: 'author' } }
+    ])
+  });
+
+  test('parse path for all things in store', function() {
+    var path = jp.parse('$.store.*');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
+      { operation: 'member', scope: 'child', expression: { type: 'wildcard', value: '*' } }
+    ])
+  });
+
+  test('parse path for price of everything in the store', function() {
+    var path = jp.parse('$.store..price');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'price' } }
+    ])
+  });
+
+  test('parse path for the last book in order via expression', function() {
+    var path = jp.parse('$..book[(@.length-1)]');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'script_expression', value: '(@.length-1)' } }
+    ])
+  });
+
+  test('parse path for the first two books via union', function() {
+    var path = jp.parse('$..book[0,1]');
+
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'union', value: [ { expression: { type: 'numeric_literal', value: '0' } }, { expression: { type: 'numeric_literal', value: '1' } } ] } }
+    ])
+  });
+
+  test('parse path for the first two books via slice', function() {
+    var path = jp.parse('$..book[0:2]');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'slice', value: '0:2' } }
+    ])
+  });
+
+  test('parse path to filter all books with isbn number', function() {
+    var path = jp.parse('$..book[?(@.isbn)]');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'filter_expression', value: '?(@.isbn)' } }
+    ])
+  });
+
+  test('parse path to filter all books with a price less than 10', function() {
+    var path = jp.parse('$..book[?(@.price<10)]');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'identifier', value: 'book' } },
+      { operation: 'subscript', scope: 'child', expression: { type: 'filter_expression', value: '?(@.price<10)' } }
+    ])
+  });
+
+  test('parse path to match all elements', function() {
+    var path = jp.parse('$..*');
+    assert.deepEqual(path, [
+      { expression: { type: 'root', value: '$' } },
+      { operation: 'member', scope: 'descendant', expression: { type: 'wildcard', value: '*' } }
+    ])
+  });
+
+  test('parse path with leading member', function() {
+    var path = jp.parse('store');
+    assert.deepEqual(path, [
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'store' } }
+    ])
+  });
+
+  test('parse path with leading member and followers', function() {
+    var path = jp.parse('Request.prototype.end');
+    assert.deepEqual(path, [
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'Request' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'prototype' } },
+      { operation: 'member', scope: 'child', expression: { type: 'identifier', value: 'end' } }
+    ])
+  });
+
+  test('parser ast is reinitialized after parse() throws', function() {
+    assert.throws(function() { var path = jp.parse('store.book...') })
+    var path = jp.parse('$..price');
+    assert.deepEqual(path, [
+      { "expression": { "type": "root", "value": "$" } },
+      { "expression": { "type": "identifier", "value": "price" }, "operation": "member", "scope": "descendant"}
+    ])
+  });
+
+});
+
+suite('parse-negative', function() {
+
+  test('parse path with leading member component throws', function() {
+    assert.throws(function(e) { var path = jp.parse('.store') }, /Expecting 'DOLLAR'/)
+  });
+
+  test('parse path with leading descendant member throws', function() {
+    assert.throws(function() { var path = jp.parse('..store') }, /Expecting 'DOLLAR'/)
+  });
+
+  test('leading script throws', function() {
+    assert.throws(function() { var path = jp.parse('()') }, /Unrecognized text/)
+  });
+
+  test('first time friendly error', function() {
+    assert.throws(function() { (new jp.JSONPath).parse('$...') }, /Expecting 'STAR'/)
+  });
+
+});
Index: frontend/node_modules/jsonpath/test/query.js
===================================================================
--- frontend/node_modules/jsonpath/test/query.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/query.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,359 @@
+var assert = require('assert');
+var jp = require('../');
+
+var data = require('./data/store.json');
+
+suite('query', function() {
+
+  test('first-level member', function() {
+    var results = jp.nodes(data, '$.store');
+    assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store } ]);
+  });
+
+  test('authors of all books in the store', function() {
+    var results = jp.nodes(data, '$.store.book[*].author');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
+      { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
+      { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
+      { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
+    ]);
+  });
+
+  test('all authors', function() {
+    var results = jp.nodes(data, '$..author');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
+      { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
+      { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
+      { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
+    ]);
+  });
+
+  test('all authors via subscript descendant string literal', function() {
+    var results = jp.nodes(data, "$..['author']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'author'], value: 'Nigel Rees' },
+      { path: ['$', 'store', 'book', 1, 'author'], value: 'Evelyn Waugh' },
+      { path: ['$', 'store', 'book', 2, 'author'], value: 'Herman Melville' },
+      { path: ['$', 'store', 'book', 3, 'author'], value: 'J. R. R. Tolkien' }
+    ]);
+  });
+
+  test('all things in store', function() {
+    var results = jp.nodes(data, '$.store.*');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book'], value: data.store.book },
+      { path: ['$', 'store', 'bicycle'], value: data.store.bicycle }
+    ]);
+  });
+
+  test('price of everything in the store', function() {
+    var results = jp.nodes(data, '$.store..price');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'price'], value: 8.95 },
+      { path: ['$', 'store', 'book', 1, 'price'], value: 12.99 },
+      { path: ['$', 'store', 'book', 2, 'price'], value: 8.99 },
+      { path: ['$', 'store', 'book', 3, 'price'], value: 22.99 },
+      { path: ['$', 'store', 'bicycle', 'price'], value: 19.95 }
+    ]);
+  });
+
+  test('last book in order via expression', function() {
+    var results = jp.nodes(data, '$..book[(@.length-1)]');
+    assert.deepEqual(results, [ { path: ['$', 'store', 'book', 3], value: data.store.book[3] }]);
+  });
+
+  test('first two books via union', function() {
+    var results = jp.nodes(data, '$..book[0,1]');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
+      { path: ['$', 'store', 'book', 1], value: data.store.book[1] }
+    ]);
+  });
+
+  test('first two books via slice', function() {
+    var results = jp.nodes(data, '$..book[0:2]');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
+      { path: ['$', 'store', 'book', 1], value: data.store.book[1] }
+    ]);
+  });
+
+  test('filter all books with isbn number', function() {
+    var results = jp.nodes(data, '$..book[?(@.isbn)]');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 2], value: data.store.book[2] },
+      { path: ['$', 'store', 'book', 3], value: data.store.book[3] }
+    ]);
+  });
+
+  test('filter all books with a price less than 10', function() {
+    var results = jp.nodes(data, '$..book[?(@.price<10)]');
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0], value: data.store.book[0] },
+      { path: ['$', 'store', 'book', 2], value: data.store.book[2] }
+    ]);
+  });
+
+  test('first ten of all elements', function() {
+    var results = jp.nodes(data, '$..*', 10);
+    assert.deepEqual(results, [
+      { path: [ '$', 'store' ], value: data.store },
+      { path: [ '$', 'store', 'book' ], value: data.store.book },
+      { path: [ '$', 'store', 'bicycle' ], value: data.store.bicycle },
+      { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] },
+      { path: [ '$', 'store', 'book', 1 ], value: data.store.book[1] },
+      { path: [ '$', 'store', 'book', 2 ], value: data.store.book[2] },
+      { path: [ '$', 'store', 'book', 3 ], value: data.store.book[3] },
+      { path: [ '$', 'store', 'book', 0, 'category' ], value: 'reference' },
+      { path: [ '$', 'store', 'book', 0, 'author' ], value: 'Nigel Rees' },
+      { path: [ '$', 'store', 'book', 0, 'title' ], value: 'Sayings of the Century' }
+    ])
+  });
+
+  test('all elements', function() {
+    var results = jp.nodes(data, '$..*');
+
+    assert.deepEqual(results, [
+      { path: [ '$', 'store' ], value: data.store },
+      { path: [ '$', 'store', 'book' ], value: data.store.book },
+      { path: [ '$', 'store', 'bicycle' ], value: data.store.bicycle },
+      { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] },
+      { path: [ '$', 'store', 'book', 1 ], value: data.store.book[1] },
+      { path: [ '$', 'store', 'book', 2 ], value: data.store.book[2] },
+      { path: [ '$', 'store', 'book', 3 ], value: data.store.book[3] },
+      { path: [ '$', 'store', 'book', 0, 'category' ], value: 'reference' },
+      { path: [ '$', 'store', 'book', 0, 'author' ], value: 'Nigel Rees' },
+      { path: [ '$', 'store', 'book', 0, 'title' ], value: 'Sayings of the Century' },
+      { path: [ '$', 'store', 'book', 0, 'price' ], value: 8.95 },
+      { path: [ '$', 'store', 'book', 1, 'category' ], value: 'fiction' },
+      { path: [ '$', 'store', 'book', 1, 'author' ], value: 'Evelyn Waugh' },
+      { path: [ '$', 'store', 'book', 1, 'title' ], value: 'Sword of Honour' },
+      { path: [ '$', 'store', 'book', 1, 'price' ], value: 12.99 },
+      { path: [ '$', 'store', 'book', 2, 'category' ], value: 'fiction' },
+      { path: [ '$', 'store', 'book', 2, 'author' ], value: 'Herman Melville' },
+      { path: [ '$', 'store', 'book', 2, 'title' ], value: 'Moby Dick' },
+      { path: [ '$', 'store', 'book', 2, 'isbn' ], value: '0-553-21311-3' },
+      { path: [ '$', 'store', 'book', 2, 'price' ], value: 8.99 },
+      { path: [ '$', 'store', 'book', 3, 'category' ], value: 'fiction' },
+      { path: [ '$', 'store', 'book', 3, 'author' ], value: 'J. R. R. Tolkien' },
+      { path: [ '$', 'store', 'book', 3, 'title' ], value: 'The Lord of the Rings' },
+      { path: [ '$', 'store', 'book', 3, 'isbn' ], value: '0-395-19395-8' },
+      { path: [ '$', 'store', 'book', 3, 'price' ], value: 22.99 },
+      { path: [ '$', 'store', 'bicycle', 'color' ], value: 'red' },
+      { path: [ '$', 'store', 'bicycle', 'price' ], value: 19.95 }
+    ]);
+  });
+
+  test('all elements via subscript wildcard', function() {
+    var results = jp.nodes(data, '$..*');
+    assert.deepEqual(jp.nodes(data, '$..[*]'), jp.nodes(data, '$..*'));
+  });
+
+  test('object subscript wildcard', function() {
+    var results = jp.query(data, '$.store[*]');
+    assert.deepEqual(results, [ data.store.book, data.store.bicycle ]);
+  });
+
+  test('no match returns empty array', function() {
+    var results = jp.nodes(data, '$..bookz');
+    assert.deepEqual(results, []);
+  });
+
+  test('member numeric literal gets first element', function() {
+    var results = jp.nodes(data, '$.store.book.0');
+    assert.deepEqual(results, [ { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] } ]);
+  });
+
+  test('member numeric literal matches string-numeric key', function() {
+    var data = { authors: { '1': 'Herman Melville', '2': 'J. R. R. Tolkien' } };
+    var results = jp.nodes(data, '$.authors.1');
+    assert.deepEqual(results, [ { path: [ '$', 'authors', 1 ], value: 'Herman Melville' } ]);
+  });
+
+  test('descendant numeric literal gets first element', function() {
+    var results = jp.nodes(data, '$.store.book..0');
+    assert.deepEqual(results, [ { path: [ '$', 'store', 'book', 0 ], value: data.store.book[0] } ]);
+  });
+
+  test('root element gets us original obj', function() {
+    var results = jp.nodes(data, '$');
+    assert.deepEqual(results, [ { path: ['$'], value: data } ]);
+  });
+
+  test('subscript double-quoted string', function() {
+    var results = jp.nodes(data, '$["store"]');
+    assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
+  });
+
+  test('subscript single-quoted string', function() {
+    var results = jp.nodes(data, "$['store']");
+    assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
+  });
+
+  test('leading member component', function() {
+    var results = jp.nodes(data, "store");
+    assert.deepEqual(results, [ { path: ['$', 'store'], value: data.store} ]);
+  });
+
+  test('union of three array slices', function() {
+    var results = jp.query(data, "$.store.book[0:1,1:2,2:3]");
+    assert.deepEqual(results, data.store.book.slice(0,3));
+  });
+
+  test('slice with step > 1', function() {
+    var results = jp.query(data, "$.store.book[0:4:2]");
+    assert.deepEqual(results, [ data.store.book[0], data.store.book[2]]);
+  });
+
+  test('union of subscript string literal keys', function() {
+    var results = jp.nodes(data, "$.store['book','bicycle']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book'], value: data.store.book },
+      { path: ['$', 'store', 'bicycle'], value: data.store.bicycle },
+    ]);
+  });
+
+  test('union of subscript string literal three keys', function() {
+    var results = jp.nodes(data, "$.store.book[0]['title','author','price']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
+      { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
+      { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price }
+    ]);
+  });
+
+  test('union of subscript integer three keys followed by member-child-identifier', function() {
+    var results = jp.nodes(data, "$.store.book[1,2,3]['title']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
+      { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
+      { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title }
+    ]);
+  });
+
+  test('union of subscript integer three keys followed by union of subscript string literal three keys', function() {
+    var results = jp.nodes(data, "$.store.book[0,1,2,3]['title','author','price']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
+      { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
+      { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
+      { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
+      { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
+      { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
+      { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
+      { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
+      { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
+      { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
+      { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
+      { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
+    ]);
+  });
+  
+  test('union of subscript integer four keys, including an inexistent one, followed by union of subscript string literal three keys', function() {
+    var results = jp.nodes(data, "$.store.book[0,1,2,3,151]['title','author','price']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
+      { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
+      { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
+      { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
+      { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
+      { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
+      { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
+      { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
+      { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
+      { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
+      { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
+      { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
+    ]);
+  });
+  
+  test('union of subscript integer three keys followed by union of subscript string literal three keys, followed by inexistent literal key', function() {
+    var results = jp.nodes(data, "$.store.book[0,1,2,3]['title','author','price','fruit']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
+      { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
+      { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
+      { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
+      { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
+      { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
+      { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
+      { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
+      { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
+      { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
+      { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
+      { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
+    ]);
+  });
+
+  test('union of subscript 4 array slices followed by union of subscript string literal three keys', function() {
+    var results = jp.nodes(data, "$.store.book[0:1,1:2,2:3,3:4]['title','author','price']");
+    assert.deepEqual(results, [
+      { path: ['$', 'store', 'book', 0, 'title'], value: data.store.book[0].title },
+      { path: ['$', 'store', 'book', 0, 'author'], value: data.store.book[0].author },
+      { path: ['$', 'store', 'book', 0, 'price'], value: data.store.book[0].price },
+      { path: ['$', 'store', 'book', 1, 'title'], value: data.store.book[1].title },
+      { path: ['$', 'store', 'book', 1, 'author'], value: data.store.book[1].author },
+      { path: ['$', 'store', 'book', 1, 'price'], value: data.store.book[1].price },
+      { path: ['$', 'store', 'book', 2, 'title'], value: data.store.book[2].title },
+      { path: ['$', 'store', 'book', 2, 'author'], value: data.store.book[2].author },
+      { path: ['$', 'store', 'book', 2, 'price'], value: data.store.book[2].price },
+      { path: ['$', 'store', 'book', 3, 'title'], value: data.store.book[3].title },
+      { path: ['$', 'store', 'book', 3, 'author'], value: data.store.book[3].author },
+      { path: ['$', 'store', 'book', 3, 'price'], value: data.store.book[3].price }
+    ]);
+  });
+
+
+  test('nested parentheses eval', function() {
+    var pathExpression = '$..book[?( @.price && (@.price + 20 || false) )]'
+    var results = jp.query(data, pathExpression);
+    assert.deepEqual(results, data.store.book);
+  });
+
+  test('array indexes from 0 to 100', function() {
+    var data = [];
+    for (var i = 0; i <= 100; ++i)
+      data[i] = Math.random();
+
+    for (var i = 0; i <= 100; ++i) {
+      var results = jp.query(data, '$[' + i.toString() +  ']');
+      assert.deepEqual(results, [data[i]]);
+    }
+  });
+
+  test('descendant subscript numeric literal', function() {
+    var data = [ 0, [ 1, 2, 3 ], [ 4, 5, 6 ] ];
+    var results = jp.query(data, '$..[0]');
+    assert.deepEqual(results, [ 0, 1, 4 ]);
+  });
+
+  test('descendant subscript numeric literal', function() {
+    var data = [ 0, 1, [ 2, 3, 4 ], [ 5, 6, 7, [ 8, 9 , 10 ] ] ];
+    var results = jp.query(data, '$..[0,1]');
+    assert.deepEqual(results, [ 0, 1, 2, 3, 5, 6, 8, 9 ]);
+  });
+
+  test('throws for no input', function() {
+    assert.throws(function() { jp.query() }, /needs to be an object/);
+  });
+
+  test('throws for bad input', function() {
+    assert.throws(function() { jp.query("string", "string") }, /needs to be an object/);
+  });
+
+  test('throws for bad input', function() {
+    assert.throws(function() { jp.query({}, null) }, /we need a path/);
+  });
+
+  test('throws for bad input', function() {
+    assert.throws(function() { jp.query({}, 42) }, /we need a path/);
+  });
+
+  test('union on objects', function() {
+    assert.deepEqual(jp.query({a: 1, b: 2, c: null}, '$..["a","b","c","d"]'), [1, 2, null]);
+  });
+
+});
+
Index: frontend/node_modules/jsonpath/test/security.js
===================================================================
--- frontend/node_modules/jsonpath/test/security.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/security.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,238 @@
+var assert = require('assert');
+var jp = require('../');
+
+suite('security', function() {
+
+  var cleanup = function() {
+    if (Object.prototype.polluted) {
+      delete Object.prototype.polluted;
+    }
+  };
+
+  teardown(function() {
+    cleanup();
+  });
+
+  test('blocks prototype pollution via value()', function() {
+    cleanup();
+    var data = {};
+    assert.throws(function() {
+      jp.value(data, '$.__proto__.polluted', 'yes');
+    }, /Unsafe key/);
+    assert.equal(({}).polluted, undefined);
+  });
+
+  test('blocks prototype pollution via apply()', function() {
+    cleanup();
+    var data = { safe: { ok: true } };
+    assert.throws(function() {
+      jp.apply(data, '$.__proto__.polluted', function() { return 'yes'; });
+    }, /Unsafe key/);
+    assert.equal(({}).polluted, undefined);
+  });
+
+  test('blocks unsafe subscript access', function() {
+    cleanup();
+    var data = {};
+    assert.throws(function() {
+      jp.query(data, '$["__proto__"]["polluted"]');
+    }, /Unsafe key/);
+    assert.equal(({}).polluted, undefined);
+  });
+
+  test('blocks unsafe union access', function() {
+    cleanup();
+    var data = { safe: 1 };
+    assert.throws(function() {
+      jp.nodes(data, "$['safe','__proto__']");
+    }, /Unsafe key/);
+    assert.equal(({}).polluted, undefined);
+  });
+
+  suite('CVE-2026-1615: blocks code injection in filter/script expressions', function() {
+    var data = { a: {}, b: [1, 2, 3] };
+
+    test('rejects constructor access in filter expression', function() {
+      assert.throws(function() {
+        jp.query(data, '$[?(@.constructor)]');
+      }, /Unsafe expression/);
+    });
+
+    test('rejects constructor.constructor in filter expression', function() {
+      assert.throws(function() {
+        jp.query(data, '$[?(@.constructor.constructor)]');
+      }, /Unsafe expression/);
+    });
+
+    test('rejects chained constructor.constructor call: @.foo["constructor"]["constructor"](...)()', function() {
+      assert.throws(function() {
+        jp.query(data, '$[?(@.foo["constructor"]["constructor"]("return process")())]');
+      }, /Unsafe expression/);
+    });
+
+    test('rejects __proto__ access in filter expression', function() {
+      assert.throws(function() {
+        jp.query(data, '$[?(@.__proto__)]');
+      }, /Unsafe expression/);
+    });
+
+    test('rejects function call in filter expression', function() {
+      assert.throws(function() {
+        jp.query(data, '$[?(process.exit(1))]');
+      }, /Unsafe expression/);
+    });
+
+    test('rejects constructor access in script expression', function() {
+      var scriptData = { a: [1, 2, 3] };
+      assert.throws(function() {
+        jp.query(scriptData, '$[(@.constructor)]');
+      }, /Unsafe expression/);
+    });
+
+    test('allows safe filter expressions', function() {
+      var storeData = { store: { book: [ { price: 5 }, { price: 15 } ] } };
+      var results = jp.query(storeData, '$..book[?(@.price<10)]');
+      assert.deepEqual(results, [ { price: 5 } ]);
+    });
+
+    test('allows safe script expressions', function() {
+      var bookData = { book: [ { id: 1 }, { id: 2 }, { id: 3 } ] };
+      var results = jp.nodes(bookData, '$..book[(@.length-1)]');
+      assert.deepEqual(results[0].value, { id: 3 });
+    });
+
+    test('rejects bracket notation constructor: @["constructor"]', function() {
+      assert.throws(function() { jp.query(data, '$[?(@["constructor"])]'); }, /Unsafe expression/);
+    });
+
+    test('rejects bracket notation __proto__: @["__proto__"]', function() {
+      assert.throws(function() { jp.query(data, '$[?(@["__proto__"])]'); }, /Unsafe expression/);
+    });
+
+    test('rejects bracket notation prototype: @["prototype"]', function() {
+      assert.throws(function() { jp.query(data, '$[?(@["prototype"])]'); }, /Unsafe expression/);
+    });
+
+    test('rejects ObjectExpression with unsafe key: { "__proto__": @ }', function() {
+      assert.throws(function() { jp.query(data, '$[?({ "__proto__": @ })]'); }, /Unsafe expression|Unexpected token/);
+    });
+
+    test('rejects ObjectExpression with unsafe key: { "constructor": @ }', function() {
+      assert.throws(function() { jp.query(data, '$[?({ "constructor": @ })]'); }, /Unsafe expression|Unexpected token/);
+    });
+
+    test('rejects ObjectExpression with unsafe key: { "prototype": @ }', function() {
+      assert.throws(function() { jp.query(data, '$[?({ "prototype": @ })]'); }, /Unsafe expression|Unexpected token/);
+    });
+
+    test('rejects unicode escape constructor in bracket: @["\\u0063onstructor"]', function() {
+      assert.throws(function() { jp.query(data, '$[?(@["\\u0063onstructor"])]'); }, /Unsafe expression/);
+    });
+
+    test('rejects unicode escape __proto__ in bracket', function() {
+      var path = '$[?(@["\\u005f\\u005fproto\\u005f\\u005f"])]';
+      assert.throws(function() { jp.query(data, path); }, /Unsafe expression/);
+    });
+
+    test('rejects IIFE: (function(){return 1})()', function() {
+      assert.throws(function() { jp.query(data, '$[?((function(){return 1})())]'); }, /Unsafe expression/);
+    });
+
+    test('rejects direct function call: process.exit(1)', function() {
+      assert.throws(function() { jp.query(data, '$[?(process.exit(1))]'); }, /Unsafe expression/);
+    });
+
+    test('rejects require() call', function() {
+      assert.throws(function() { jp.query(data, '$[?(require("fs"))]'); }, /Unsafe expression/);
+    });
+
+    test('rejects eval() call', function() {
+      assert.throws(function() { jp.query(data, '$[?(eval("1"))]'); }, /Unsafe expression/);
+    });
+
+    test('rejects globalThis / global identifier', function() {
+      assert.throws(function() { jp.query(data, '$[?(globalThis)]'); }, /Unsafe expression/);
+      assert.throws(function() { jp.query(data, '$[?(global)]'); }, /Unsafe expression/);
+    });
+
+    test('rejects NewExpression: new Function("return 1")()', function() {
+      assert.throws(function() { jp.query(data, '$[?(new Function("return 1")())]'); }, /Unsafe expression/);
+    });
+
+    test('rejects JSFuck-style: [] ["filter"]["constructor"]', function() {
+      assert.throws(function() { jp.query(data, '$[?([]["filter"]["constructor"])]'); }, /Unsafe expression/);
+    });
+
+    test('rejects JSFuck-style constructor call (no @)', function() {
+      assert.throws(function() { jp.query(data, '$[?([]["filter"]["constructor"]("return 1")())]'); }, /Unsafe expression/);
+    });
+
+    test('rejects sequence expression: (1, process.exit)(1)', function() {
+      assert.throws(function() { jp.query(data, '$[?((1, process.exit)(1))]'); }, /Unsafe expression/);
+    });
+
+    test('rejects method call on @: @.valueOf()', function() {
+      assert.throws(function() { jp.query(data, '$[?(@.valueOf())]'); }, /Unsafe expression/);
+    });
+
+    test('rejects method call: @.toString()', function() {
+      assert.throws(function() { jp.query(data, '$[?(@.toString())]'); }, /Unsafe expression/);
+    });
+
+    test('rejects template literal in computed: @[`constructor`]', function() {
+      assert.throws(function() { jp.query(data, '$[?(@[`constructor`])]'); }, /Unsafe expression|Unexpected token|ILLEGAL/);
+    });
+
+    test('rejects tagged template (code execution vector)', function() {
+      assert.throws(function() { jp.query(data, '$[?(String.raw`x`)]'); }, /Unsafe expression|Unexpected token|ILLEGAL/);
+    });
+
+    test('rejects ArrowFunctionExpression', function() {
+      assert.throws(function() { jp.query(data, '$[?((()=>1)())]'); }, /Unsafe expression|Unexpected token/);
+    });
+
+    test('rejects ThisExpression (this)', function() {
+      assert.throws(function() { jp.query(data, '$[?(this)]'); }, /Unsafe expression/);
+    });
+
+    test('rejects script expression with constructor', function() {
+      assert.throws(function() { jp.query(data, '$[(@.constructor)]'); }, /Unsafe expression/);
+    });
+
+    test('rejects script expression with call', function() {
+      assert.throws(function() { jp.query(data, '$[((function(){return 0})())]'); }, /Unsafe expression/);
+    });
+
+    test('allows @.length (no call)', function() {
+      var r = jp.query(data, '$[?(@.length)]');
+      assert.ok(Array.isArray(r));
+    });
+
+    test('allows bracket with safe key: @["length"]', function() {
+      var r = jp.query(data, '$[?(@["length"])]');
+      assert.ok(Array.isArray(r));
+    });
+
+    test('allows @["@class"] (existing test pattern)', function() {
+      var d = { DIV: [{ '@class': 'value', val: 5 }] };
+      var r = jp.query(d, '$..DIV[?(@["@class"]=="value")]');
+      assert.deepEqual(r, d.DIV);
+    });
+
+    test('rejects prototype access in filter', function() {
+      assert.throws(function() { jp.query(data, '$[?(@.prototype)]'); }, /Unsafe expression/);
+    });
+
+    test('rejects comma/sequence that could hide call', function() {
+      assert.throws(function() { jp.query(data, '$[?((0, eval)("1"))]'); }, /Unsafe expression/);
+    });
+
+    test('rejects AssignmentExpression', function() {
+      assert.throws(function() { jp.query(data, '$[?((x=1)==1)]'); }, /Unsafe expression/);
+    });
+
+    test('rejects UpdateExpression (++, --)', function() {
+      assert.throws(function() { jp.query(data, '$[?(@.x++)]'); }, /Unsafe expression/);
+    });
+  });
+});
Index: frontend/node_modules/jsonpath/test/slice.js
===================================================================
--- frontend/node_modules/jsonpath/test/slice.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/slice.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+var assert = require('assert');
+var slice = require('../lib/slice');
+
+var data = ['a', 'b', 'c', 'd', 'e', 'f'];
+
+suite('slice', function() {
+
+  test('no params yields copy', function() {
+    assert.deepEqual(slice(data), data);
+  });
+
+  test('no end param defaults to end', function() {
+    assert.deepEqual(slice(data, 2), data.slice(2));
+  });
+
+  test('zero end param yields empty', function() {
+    assert.deepEqual(slice(data, 0, 0), []);
+  });
+
+  test('first element with explicit params', function() {
+    assert.deepEqual(slice(data, 0, 1, 1), ['a']);
+  });
+
+  test('last element with explicit params', function() {
+    assert.deepEqual(slice(data, -1, 6), ['f']);
+  });
+
+  test('empty extents and negative step reverses', function() {
+    assert.deepEqual(slice(data, null, null, -1), ['f', 'e', 'd', 'c', 'b', 'a']);
+  });
+
+  test('negative step partial slice', function() {
+    assert.deepEqual(slice(data, 4, 2, -1), ['e', 'd']);
+  });
+
+  test('negative step partial slice no start defaults to end', function() {
+    assert.deepEqual(slice(data, null, 2, -1), ['f', 'e', 'd']);
+  });
+
+  test('extents clamped end', function() {
+    assert.deepEqual(slice(data, null, 100), data);
+  });
+
+  test('extents clamped beginning', function() {
+    assert.deepEqual(slice(data, -100, 100), data);
+  });
+
+  test('backwards extents yields empty', function() {
+    assert.deepEqual(slice(data, 2, 1), []);
+  });
+
+  test('zero step gets shot down', function() {
+    assert.throws(function() { slice(data, null, null, 0) });
+  });
+
+});
+
Index: frontend/node_modules/jsonpath/test/stringify.js
===================================================================
--- frontend/node_modules/jsonpath/test/stringify.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/stringify.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+var assert = require('assert');
+var jp = require('../');
+
+suite('stringify', function() {
+
+  test('simple path stringifies', function() {
+    var string = jp.stringify(['$', 'a', 'b', 'c']);
+    assert.equal(string, '$.a.b.c');
+  });
+
+  test('numeric literals end up as subscript numbers', function() {
+    var string = jp.stringify(['$', 'store', 'book', 0, 'author']);
+    assert.equal(string, '$.store.book[0].author');
+  });
+
+  test('simple path with no leading root stringifies', function() {
+    var string = jp.stringify(['a', 'b', 'c']);
+    assert.equal(string, '$.a.b.c');
+  });
+
+  test('simple parsed path stringifies', function() {
+    var path = [
+      { scope: 'child', operation: 'member', expression: { type: 'identifier', value: 'a' } },
+      { scope: 'child', operation: 'member', expression: { type: 'identifier', value: 'b' } },
+      { scope: 'child', operation: 'member', expression: { type: 'identifier', value: 'c' } }
+    ];
+    var string = jp.stringify(path);
+    assert.equal(string, '$.a.b.c');
+  });
+
+  test('keys with hyphens get subscripted', function() {
+    var string = jp.stringify(['$', 'member-search']);
+    assert.equal(string, '$["member-search"]');
+  });
+
+  test('complicated path round trips', function() {
+    var pathExpression = '$..*[0:2].member["string-xyz"]';
+    var path = jp.parse(pathExpression);
+    var string = jp.stringify(path);
+    assert.equal(string, pathExpression);
+  });
+
+  test('complicated path with filter exp round trips', function() {
+    var pathExpression = '$..*[0:2].member[?(@.val > 10)]';
+    var path = jp.parse(pathExpression);
+    var string = jp.stringify(path);
+    assert.equal(string, pathExpression);
+  });
+
+  test('throws for no input', function() {
+    assert.throws(function() { jp.stringify() }, /we need a path/);
+  });
+
+});
Index: frontend/node_modules/jsonpath/test/sugar.js
===================================================================
--- frontend/node_modules/jsonpath/test/sugar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsonpath/test/sugar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,72 @@
+var assert = require('assert');
+var jp = require('../');
+var util = require('util');
+
+suite('sugar', function() {
+
+  test('parent gets us parent value', function() {
+    var data = { a: 1, b: 2, c: 3, z: { a: 100, b: 200 } };
+    var parent = jp.parent(data, '$.z.b');
+    assert.equal(parent, data.z);
+  });
+
+  test('apply method sets values', function() {
+    var data = { a: 1, b: 2, c: 3, z: { a: 100, b: 200 } };
+    jp.apply(data, '$..a', function(v) { return v + 1 });
+    assert.equal(data.a, 2);
+    assert.equal(data.z.a, 101);
+  });
+
+  test('apply method applies survives structural changes', function() {
+    var data = {a: {b: [1, {c: [2,3]}]}};
+    jp.apply(data, '$..*[?(@.length > 1)]', function(array) {
+      return array.reverse();
+    });
+    assert.deepEqual(data.a.b, [{c: [3, 2]}, 1]);
+  });
+
+  test('value method gets us a value', function() {
+    var data = { a: 1, b: 2, c: 3, z: { a: 100, b: 200 } };
+    var b = jp.value(data, '$..b')
+    assert.equal(b, data.b);
+  });
+
+  test('value method sets us a value', function() {
+    var data = { a: 1, b: 2, c: 3, z: { a: 100, b: 200 } };
+    var b = jp.value(data, '$..b', '5000')
+    assert.equal(b, 5000);
+    assert.equal(data.b, 5000);
+  });
+
+  test('value method sets new key and value', function() {
+    var data = {};
+    var a = jp.value(data, '$.a', 1);
+    var c = jp.value(data, '$.b.c', 2);
+    assert.equal(a, 1);
+    assert.equal(data.a, 1);
+    assert.equal(c, 2);
+    assert.equal(data.b.c, 2);
+  });
+
+  test('value method sets new array value', function() {
+    var data = {};
+    var v1 = jp.value(data, '$.a.d[0]', 4);
+    var v2 = jp.value(data, '$.a.d[1]', 5);
+    assert.equal(v1, 4);
+    assert.equal(v2, 5);
+    assert.deepEqual(data.a.d, [4, 5]);
+  });
+
+  test('value method sets non-literal key', function() {
+    var data = { "list": [ { "index": 0, "value": "default" }, { "index": 1, "value": "default" } ] };
+    jp.value(data, '$.list[?(@.index == 1)].value', "test");
+    assert.equal(data.list[1].value, "test");
+  });
+
+  test('paths with a count gets us back count many paths', function() {
+    data = [ { a: [ 1, 2, 3 ], b: [ -1, -2, -3 ] }, { } ]
+    paths = jp.paths(data, '$..*', 3)
+    assert.deepEqual(paths, [ ['$', '0'], ['$', '1'], ['$', '0', 'a'] ]);
+  });
+
+});
