json-p3 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,6 +39,7 @@ console.log(nodes.values()); // [ 'John', 'Sally', 'Jane' ]
39
39
 
40
40
  - Docs: https://jg-rp.github.io/json-p3/
41
41
  - Install: https://jg-rp.github.io/json-p3/#install
42
+ - JSONPath playground: https://jg-rp.github.io/json-p3/playground
42
43
  - JSONPath syntax: https://jg-rp.github.io/json-p3/guides/jsonpath-syntax
43
44
  - API reference: https://jg-rp.github.io/json-p3/api
44
45
  - Change log: https://github.com/jg-rp/json-p3/blob/main/CHANGELOG.md
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export declare const version = "__VERSION__";
2
2
  export * as jsonpath from "./path";
3
- export { DEFAULT_ENVIRONMENT, FunctionExpressionType, JSONPath, JSONPathEnvironment, JSONPathError, JSONPathIndexError, JSONPathLexerError, JSONPathNode, JSONPathNodeList, JSONPathSyntaxError, JSONPathTypeError, JSONPathRecursionLimitError, Token, TokenKind, Nothing, query, compile, } from "./path";
3
+ export { DEFAULT_ENVIRONMENT, FunctionExpressionType, JSONPath, JSONPathEnvironment, JSONPathError, JSONPathIndexError, JSONPathLexerError, JSONPathNode, JSONPathNodeList, JSONPathSyntaxError, JSONPathTypeError, JSONPathRecursionLimitError, Token, TokenKind, Nothing, lazyQuery, query, compile, } from "./path";
4
4
  export type { JSONPathEnvironmentOptions, FilterFunction } from "./path";
5
5
  export * as jsonpointer from "./pointer";
6
6
  export { JSONPointer, RelativeJSONPointer, resolve, UNDEFINED, } from "./pointer";
@@ -1,5 +1,5 @@
1
1
  /*
2
- * json-p3 version 0.2.0
2
+ * json-p3 version 0.3.1
3
3
  * https://github.com/jg-rp/json-p3
4
4
  *
5
5
  * MIT License
@@ -645,10 +645,6 @@ var index$3 = /*#__PURE__*/Object.freeze({
645
645
  * The pair of a JSON value and its location found in the target JSON value.
646
646
  */
647
647
  class JSONPathNode {
648
- /**
649
- * The normalized path to this node in the target JSON value.
650
- */
651
-
652
648
  /**
653
649
  * @param value - The JSON value found at _location_.
654
650
  * @param location - The parts of a normalized path to _value_.
@@ -658,9 +654,12 @@ class JSONPathNode {
658
654
  this.value = value;
659
655
  this.location = location;
660
656
  this.root = root;
661
- this.path =
662
- // eslint-disable-next-line prefer-template
663
- "$" + location.map(s => isString(s) ? `['${s}']` : `[${s}]`).join("");
657
+ }
658
+ get path() {
659
+ return (
660
+ // eslint-disable-next-line prefer-template
661
+ "$" + this.location.map(s => isString(s) ? `['${s}']` : `[${s}]`).join("")
662
+ );
664
663
  }
665
664
 
666
665
  /**
@@ -912,7 +911,7 @@ class JSONPathQuery extends FilterExpression {
912
911
  }
913
912
  class RelativeQuery extends JSONPathQuery {
914
913
  evaluate(context) {
915
- return this.path.query(context.currentValue);
914
+ return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.currentValue))) : this.path.query(context.currentValue);
916
915
  }
917
916
  toString() {
918
917
  return `@${this.path.toString().slice(1)}`;
@@ -920,7 +919,7 @@ class RelativeQuery extends JSONPathQuery {
920
919
  }
921
920
  class RootQuery extends JSONPathQuery {
922
921
  evaluate(context) {
923
- return this.path.query(context.rootValue);
922
+ return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.rootValue))) : this.path.query(context.rootValue);
924
923
  }
925
924
  toString() {
926
925
  return this.path.toString();
@@ -938,12 +937,26 @@ class FunctionExtension extends FilterExpression {
938
937
  if (!func) {
939
938
  throw new UndefinedFilterFunctionError(`filter function '${this.name}' is undefined`, this.token);
940
939
  }
941
- const args = this.args.map(arg => arg.evaluate(context)).map((arg, idx) => func.argTypes[idx] !== FunctionExpressionType.NodesType && arg instanceof JSONPathNodeList ? arg.valuesOrSingular() : arg);
940
+ const args = this.args.map(arg => arg.evaluate(context)).map((arg, idx) => func.argTypes[idx] !== FunctionExpressionType.NodesType && arg instanceof JSONPathNodeList ? this.unpack_node_list(arg) : arg);
942
941
  return func.call(...args);
943
942
  }
944
943
  toString() {
945
944
  return `${this.name}(${this.args.map(e => e.toString()).join(", ")})`;
946
945
  }
946
+ unpack_node_list(arg) {
947
+ switch (arg.length) {
948
+ case 0:
949
+ // If the query results in an empty node list, the argument
950
+ // is the special result Nothing.
951
+ return Nothing;
952
+ case 1:
953
+ // If the query results in a node list consisting of a single
954
+ // node, the argument is the value of the node
955
+ return arg.nodes[0].value;
956
+ default:
957
+ return arg;
958
+ }
959
+ }
947
960
  }
948
961
 
949
962
  /**
@@ -1722,6 +1735,10 @@ class JSONPathSelector {
1722
1735
  this.token = token;
1723
1736
  }
1724
1737
 
1738
+ /**
1739
+ * @param nodes - Nodes matched by preceding selectors.
1740
+ */
1741
+
1725
1742
  /**
1726
1743
  * @param nodes - Nodes matched by preceding selectors.
1727
1744
  */
@@ -1749,7 +1766,14 @@ class NameSelector extends JSONPathSelector {
1749
1766
  rv.push(new JSONPathNode(node.value[this.name], node.location.concat(this.name), node.root));
1750
1767
  }
1751
1768
  }
1752
- return new JSONPathNodeList(rv);
1769
+ return rv;
1770
+ }
1771
+ *lazyResolve(nodes) {
1772
+ for (const node of nodes) {
1773
+ if (hasStringKey(node.value, this.name)) {
1774
+ yield new JSONPathNode(node.value[this.name], node.location.concat(this.name), node.root);
1775
+ }
1776
+ }
1753
1777
  }
1754
1778
  toString() {
1755
1779
  return this.shorthand ? `['${this.name}']` : `'${this.name}'`;
@@ -1779,7 +1803,17 @@ class IndexSelector extends JSONPathSelector {
1779
1803
  }
1780
1804
  }
1781
1805
  }
1782
- return new JSONPathNodeList(rv);
1806
+ return rv;
1807
+ }
1808
+ *lazyResolve(nodes) {
1809
+ for (const node of nodes) {
1810
+ if (isArray(node.value)) {
1811
+ const normIndex = this.normalizedIndex(node.value.length);
1812
+ if (normIndex in node.value) {
1813
+ yield new JSONPathNode(node.value[normIndex], node.location.concat(normIndex), node.root);
1814
+ }
1815
+ }
1816
+ }
1783
1817
  }
1784
1818
  toString() {
1785
1819
  return String(this.index);
@@ -1807,7 +1841,15 @@ class SliceSelector extends JSONPathSelector {
1807
1841
  rv.push(new JSONPathNode(value, node.location.concat(i), node.root));
1808
1842
  }
1809
1843
  }
1810
- return new JSONPathNodeList(rv);
1844
+ return rv;
1845
+ }
1846
+ *lazyResolve(nodes) {
1847
+ for (const node of nodes) {
1848
+ if (!isArray(node.value)) continue;
1849
+ for (const [i, value] of this.slice(node.value, this.start, this.stop, this.step)) {
1850
+ yield new JSONPathNode(value, node.location.concat(i), node.root);
1851
+ }
1852
+ }
1811
1853
  }
1812
1854
  toString() {
1813
1855
  const start = this.start ? this.start : "";
@@ -1890,22 +1932,92 @@ class WildcardSelector extends JSONPathSelector {
1890
1932
  }
1891
1933
  }
1892
1934
  }
1893
- return new JSONPathNodeList(rv);
1935
+ return rv;
1936
+ }
1937
+ *lazyResolve(nodes) {
1938
+ for (const node of nodes) {
1939
+ if (node.value instanceof String) continue;
1940
+ if (isArray(node.value)) {
1941
+ for (let i = 0; i < node.value.length; i++) {
1942
+ yield new JSONPathNode(node.value[i], node.location.concat(i), node.root);
1943
+ }
1944
+ } else if (isObject(node.value)) {
1945
+ for (const [key, value] of Object.entries(node.value)) {
1946
+ yield new JSONPathNode(value, node.location.concat(key), node.root);
1947
+ }
1948
+ }
1949
+ }
1894
1950
  }
1895
1951
  toString() {
1896
1952
  return this.shorthand ? "[*]" : "*";
1897
1953
  }
1898
1954
  }
1899
1955
  class RecursiveDescentSegment extends JSONPathSelector {
1956
+ constructor(environment, token, selector) {
1957
+ super(environment, token);
1958
+ this.environment = environment;
1959
+ this.token = token;
1960
+ this.selector = selector;
1961
+ }
1900
1962
  resolve(nodes) {
1901
1963
  const rv = [];
1902
1964
  for (const node of nodes) {
1903
- rv.push(node, ...this.visit(node));
1965
+ rv.push(node);
1966
+ for (const _node of this.visit(node)) {
1967
+ rv.push(_node);
1968
+ }
1969
+ }
1970
+ return this.selector.resolve(rv);
1971
+ }
1972
+ *lazyResolve(nodes) {
1973
+ yield* this.selector.lazyResolve(this._lazyResolve(nodes));
1974
+ }
1975
+
1976
+ // eslint-disable-next-line sonarjs/cognitive-complexity
1977
+ *_lazyResolve(nodes) {
1978
+ for (const _node of nodes) {
1979
+ const stack = [{
1980
+ node: _node,
1981
+ depth: 0
1982
+ }];
1983
+ yield _node;
1984
+ while (stack.length) {
1985
+ const {
1986
+ node: currentNode,
1987
+ depth
1988
+ } = stack.pop();
1989
+ if (depth >= this.environment.maxRecursionDepth) {
1990
+ throw new JSONPathRecursionLimitError("recursion limit reached", this.token);
1991
+ }
1992
+ if (currentNode.value instanceof String) continue;
1993
+ if (isArray(currentNode.value)) {
1994
+ for (let i = 0; i < currentNode.value.length; i++) {
1995
+ const __node = new JSONPathNode(currentNode.value[i], currentNode.location.concat(i), currentNode.root);
1996
+ yield __node;
1997
+ if (isObject(__node.value)) {
1998
+ stack.push({
1999
+ node: __node,
2000
+ depth: depth + 1
2001
+ });
2002
+ }
2003
+ }
2004
+ } else if (isObject(currentNode.value)) {
2005
+ for (const [key, value] of Object.entries(currentNode.value)) {
2006
+ const __node = new JSONPathNode(value, currentNode.location.concat(key), currentNode.root);
2007
+ yield __node;
2008
+ if (isObject(__node.value)) {
2009
+ stack.push({
2010
+ node: __node,
2011
+ depth: depth + 1
2012
+ });
2013
+ }
2014
+ }
2015
+ }
2016
+ }
1904
2017
  }
1905
- return new JSONPathNodeList(rv);
1906
2018
  }
1907
2019
  toString() {
1908
- return "..";
2020
+ return `..${this.selector.toString()}`;
1909
2021
  }
1910
2022
  visit(node) {
1911
2023
  let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -1913,19 +2025,25 @@ class RecursiveDescentSegment extends JSONPathSelector {
1913
2025
  throw new JSONPathRecursionLimitError("recursion limit reached", this.token);
1914
2026
  }
1915
2027
  const rv = [];
1916
- if (node.value instanceof String) return new JSONPathNodeList(rv);
2028
+ if (node.value instanceof String) return rv;
1917
2029
  if (isArray(node.value)) {
1918
2030
  for (let i = 0; i < node.value.length; i++) {
1919
2031
  const _node = new JSONPathNode(node.value[i], node.location.concat(i), node.root);
1920
- rv.push(_node, ...this.visit(_node, depth + 1));
2032
+ rv.push(_node);
2033
+ for (const __node of this.visit(_node, depth + 1)) {
2034
+ rv.push(__node);
2035
+ }
1921
2036
  }
1922
2037
  } else if (isObject(node.value)) {
1923
2038
  for (const [key, value] of Object.entries(node.value)) {
1924
2039
  const _node = new JSONPathNode(value, node.location.concat(key), node.root);
1925
- rv.push(_node, ...this.visit(_node, depth + 1));
2040
+ rv.push(_node);
2041
+ for (const __node of this.visit(_node, depth + 1)) {
2042
+ rv.push(__node);
2043
+ }
1926
2044
  }
1927
2045
  }
1928
- return new JSONPathNodeList(rv);
2046
+ return rv;
1929
2047
  }
1930
2048
  }
1931
2049
  class FilterSelector extends JSONPathSelector {
@@ -1966,7 +2084,40 @@ class FilterSelector extends JSONPathSelector {
1966
2084
  }
1967
2085
  }
1968
2086
  }
1969
- return new JSONPathNodeList(rv);
2087
+ return rv;
2088
+ }
2089
+
2090
+ // eslint-disable-next-line sonarjs/cognitive-complexity
2091
+ *lazyResolve(nodes) {
2092
+ for (const node of nodes) {
2093
+ if (node.value instanceof String) continue;
2094
+ if (isArray(node.value)) {
2095
+ for (let i = 0; i < node.value.length; i++) {
2096
+ const value = node.value[i];
2097
+ const filterContext = {
2098
+ environment: this.environment,
2099
+ currentValue: value,
2100
+ rootValue: node.root,
2101
+ lazy: true
2102
+ };
2103
+ if (this.expression.evaluate(filterContext)) {
2104
+ yield new JSONPathNode(value, node.location.concat(i), node.root);
2105
+ }
2106
+ }
2107
+ } else if (isObject(node.value)) {
2108
+ for (const [key, value] of Object.entries(node.value)) {
2109
+ const filterContext = {
2110
+ environment: this.environment,
2111
+ currentValue: value,
2112
+ rootValue: node.root,
2113
+ lazy: true
2114
+ };
2115
+ if (this.expression.evaluate(filterContext)) {
2116
+ yield new JSONPathNode(value, node.location.concat(key), node.root);
2117
+ }
2118
+ }
2119
+ }
2120
+ }
1970
2121
  }
1971
2122
  toString() {
1972
2123
  return `?${this.expression.toString()}`;
@@ -1983,10 +2134,19 @@ class BracketedSelection extends JSONPathSelector {
1983
2134
  const rv = [];
1984
2135
  for (const node of nodes) {
1985
2136
  for (const item of this.items) {
1986
- rv.push(...item.resolve(new JSONPathNodeList([node])));
2137
+ for (const _node of item.resolve([node])) {
2138
+ rv.push(_node);
2139
+ }
2140
+ }
2141
+ }
2142
+ return rv;
2143
+ }
2144
+ *lazyResolve(nodes) {
2145
+ for (const node of nodes) {
2146
+ for (const item of this.items) {
2147
+ yield* item.lazyResolve([node]);
1987
2148
  }
1988
2149
  }
1989
- return new JSONPathNodeList(rv);
1990
2150
  }
1991
2151
  toString() {
1992
2152
  return `[${this.items.map(itm => itm.toString()).join(", ")}]`;
@@ -2025,10 +2185,23 @@ class JSONPath {
2025
2185
  * @returns
2026
2186
  */
2027
2187
  query(value) {
2028
- let nodes = new JSONPathNodeList([new JSONPathNode(value, [], value)]);
2188
+ let nodes = [new JSONPathNode(value, [], value)];
2029
2189
  for (const selector of this.selectors) {
2030
2190
  nodes = selector.resolve(nodes);
2031
2191
  }
2192
+ return new JSONPathNodeList(nodes);
2193
+ }
2194
+
2195
+ /**
2196
+ *
2197
+ * @param value -
2198
+ * @returns
2199
+ */
2200
+ lazyQuery(value) {
2201
+ let nodes = [new JSONPathNode(value, [], value)][Symbol.iterator]();
2202
+ for (const selector of this.selectors) {
2203
+ nodes = selector.lazyResolve(nodes);
2204
+ }
2032
2205
  return nodes;
2033
2206
  }
2034
2207
 
@@ -2041,7 +2214,10 @@ class JSONPath {
2041
2214
  * there are no matches.
2042
2215
  */
2043
2216
  match(value) {
2044
- return this.query(value).nodes.at(0);
2217
+ const it = this.lazyQuery(value);
2218
+ const rv = it.next();
2219
+ if (rv.done) return undefined;
2220
+ return rv.value;
2045
2221
  }
2046
2222
 
2047
2223
  /**
@@ -2061,13 +2237,17 @@ class JSONPath {
2061
2237
  }
2062
2238
 
2063
2239
  const PRECEDENCE_LOWEST = 1;
2064
- const PRECEDENCE_LOGICALRIGHT = 3;
2065
2240
  const PRECEDENCE_LOGICAL_AND = 4;
2066
2241
  const PRECEDENCE_LOGICAL_OR = 5;
2067
2242
  const PRECEDENCE_COMPARISON = 6;
2068
- const PRECEDENCES = new Map([[TokenKind.AND, PRECEDENCE_LOGICAL_AND], [TokenKind.EQ, PRECEDENCE_COMPARISON], [TokenKind.GE, PRECEDENCE_COMPARISON], [TokenKind.GT, PRECEDENCE_COMPARISON], [TokenKind.LE, PRECEDENCE_COMPARISON], [TokenKind.LT, PRECEDENCE_COMPARISON], [TokenKind.NE, PRECEDENCE_COMPARISON], [TokenKind.NOT, PRECEDENCE_LOGICALRIGHT], [TokenKind.OR, PRECEDENCE_LOGICAL_OR], [TokenKind.RPAREN, PRECEDENCE_LOWEST]]);
2243
+ const PRECEDENCE_PREFIX = 7;
2244
+ const PRECEDENCES = new Map([[TokenKind.AND, PRECEDENCE_LOGICAL_AND], [TokenKind.EQ, PRECEDENCE_COMPARISON], [TokenKind.GE, PRECEDENCE_COMPARISON], [TokenKind.GT, PRECEDENCE_COMPARISON], [TokenKind.LE, PRECEDENCE_COMPARISON], [TokenKind.LT, PRECEDENCE_COMPARISON], [TokenKind.NE, PRECEDENCE_COMPARISON], [TokenKind.NOT, PRECEDENCE_PREFIX], [TokenKind.OR, PRECEDENCE_LOGICAL_OR], [TokenKind.RPAREN, PRECEDENCE_LOWEST]]);
2069
2245
  const BINARY_OPERATORS = new Map([[TokenKind.AND, "&&"], [TokenKind.EQ, "=="], [TokenKind.GE, ">="], [TokenKind.GT, ">"], [TokenKind.LE, "<="], [TokenKind.LT, "<"], [TokenKind.NE, "!="], [TokenKind.OR, "||"]]);
2070
2246
  const COMPARISON_OPERATORS = new Set(["==", ">=", ">", "<=", "<", "!="]);
2247
+
2248
+ /**
2249
+ * JSONPath token stream parser.
2250
+ */
2071
2251
  class Parser {
2072
2252
  constructor(environment) {
2073
2253
  this.environment = environment;
@@ -2084,30 +2264,41 @@ class Parser {
2084
2264
  parsePath(stream) {
2085
2265
  let inFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2086
2266
  const selectors = [];
2087
- loop: for (;;) {
2088
- switch (stream.current.kind) {
2089
- case TokenKind.NAME:
2090
- selectors.push(new NameSelector(this.environment, stream.current, stream.current.value, true));
2091
- break;
2092
- case TokenKind.WILD:
2093
- selectors.push(new WildcardSelector(this.environment, stream.current, true));
2094
- break;
2095
- case TokenKind.DDOT:
2096
- selectors.push(new RecursiveDescentSegment(this.environment, stream.current));
2097
- break;
2098
- case TokenKind.LBRACKET:
2099
- selectors.push(this.parseBracketedSelection(stream));
2100
- break;
2101
- default:
2102
- if (inFilter) {
2103
- stream.backup();
2104
- }
2105
- break loop;
2267
+ for (;;) {
2268
+ const selector = this.parseSegment(stream);
2269
+ if (!selector) {
2270
+ if (inFilter) {
2271
+ stream.backup();
2272
+ }
2273
+ break;
2106
2274
  }
2275
+ selectors.push(selector);
2107
2276
  stream.next();
2108
2277
  }
2109
2278
  return selectors;
2110
2279
  }
2280
+ parseSegment(stream) {
2281
+ switch (stream.current.kind) {
2282
+ case TokenKind.NAME:
2283
+ return new NameSelector(this.environment, stream.current, stream.current.value, true);
2284
+ case TokenKind.WILD:
2285
+ return new WildcardSelector(this.environment, stream.current, true);
2286
+ case TokenKind.DDOT:
2287
+ {
2288
+ const segmentToken = stream.current;
2289
+ stream.next();
2290
+ const selector = this.parseSegment(stream);
2291
+ if (!selector) {
2292
+ throw new JSONPathSyntaxError("bald descendant segment", stream.current);
2293
+ }
2294
+ return new RecursiveDescentSegment(this.environment, segmentToken, selector);
2295
+ }
2296
+ case TokenKind.LBRACKET:
2297
+ return this.parseBracketedSelection(stream);
2298
+ default:
2299
+ return null;
2300
+ }
2301
+ }
2111
2302
  parseIndex(stream) {
2112
2303
  if (stream.current.value.length > 1 && stream.current.value.startsWith("0") || stream.current.value.startsWith("-0")) {
2113
2304
  throw new JSONPathSyntaxError("leading zero in index selector", stream.current);
@@ -2228,7 +2419,7 @@ class Parser {
2228
2419
  parsePrefixExpression(stream) {
2229
2420
  stream.expect(TokenKind.NOT);
2230
2421
  stream.next();
2231
- return new PrefixExpression(stream.current, "!", this.parseFilterExpression(stream, PRECEDENCE_LOGICALRIGHT));
2422
+ return new PrefixExpression(stream.current, "!", this.parseFilterExpression(stream, PRECEDENCE_PREFIX));
2232
2423
  }
2233
2424
  parseInfixExpression(stream, left) {
2234
2425
  const tok = stream.next();
@@ -2238,11 +2429,9 @@ class Parser {
2238
2429
  if (!operator) {
2239
2430
  throw new JSONPathSyntaxError(`unknown operator '${tok.kind}'`, tok);
2240
2431
  }
2241
- this.throwForNonSingularQuery(left);
2242
- this.throwForNonSingularQuery(right);
2243
2432
  if (COMPARISON_OPERATORS.has(operator)) {
2244
- this.throwForNonComparableFunction(left);
2245
- this.throwForNonComparableFunction(right);
2433
+ this.throwForNonComparable(left);
2434
+ this.throwForNonComparable(right);
2246
2435
  }
2247
2436
  return new InfixExpression(tok, left, operator, right);
2248
2437
  }
@@ -2330,16 +2519,15 @@ class Parser {
2330
2519
  throw new JSONPathSyntaxError(`invalid ${isName ? "name selector" : "string literal"} '${token.value}'`, token);
2331
2520
  }
2332
2521
  }
2333
- throwForNonSingularQuery(expr) {
2522
+ throwForNonComparable(expr) {
2334
2523
  if ((expr instanceof RootQuery || expr instanceof RelativeQuery) && !expr.path.singularQuery()) {
2335
- throw new JSONPathSyntaxError("non-singular query is not comparable", expr.token);
2524
+ throw new JSONPathTypeError("non-singular query is not comparable", expr.token);
2336
2525
  }
2337
- }
2338
- throwForNonComparableFunction(expr) {
2339
- if (!(expr instanceof FunctionExtension)) return;
2340
- const func = this.environment.functionRegister.get(expr.name);
2341
- if (func && func.returnType !== FunctionExpressionType.ValueType) {
2342
- throw new JSONPathTypeError(`result of ${expr.name}() is not comparable`, expr.token);
2526
+ if (expr instanceof FunctionExtension) {
2527
+ const func = this.environment.functionRegister.get(expr.name);
2528
+ if (func && func.returnType !== FunctionExpressionType.ValueType) {
2529
+ throw new JSONPathTypeError(`result of ${expr.name}() is not comparable`, expr.token);
2530
+ }
2343
2531
  }
2344
2532
  }
2345
2533
  }
@@ -2350,7 +2538,10 @@ class Parser {
2350
2538
  */
2351
2539
 
2352
2540
  /**
2541
+ * A configuration object from which JSONPath queries can be evaluated.
2353
2542
  *
2543
+ * An environment is where you'd register custom function extensions or set
2544
+ * the maximum recursion depth limit, for example.
2354
2545
  */
2355
2546
  class JSONPathEnvironment {
2356
2547
  /**
@@ -2383,8 +2574,7 @@ class JSONPathEnvironment {
2383
2574
  */
2384
2575
  functionRegister = new Map();
2385
2576
  /**
2386
- *
2387
- * @param options -
2577
+ * @param options - Environment configuration options.
2388
2578
  */
2389
2579
  constructor() {
2390
2580
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -2397,9 +2587,8 @@ class JSONPathEnvironment {
2397
2587
  }
2398
2588
 
2399
2589
  /**
2400
- *
2401
- * @param path -
2402
- * @returns
2590
+ * @param path - A JSONPath query to parse.
2591
+ * @returns A new {@link JSONPath} object, bound to this environment.
2403
2592
  */
2404
2593
  compile(path) {
2405
2594
  return new JSONPath(this, this.parser.parse(new TokenStream(tokenize(path))));
@@ -2407,14 +2596,28 @@ class JSONPathEnvironment {
2407
2596
 
2408
2597
  /**
2409
2598
  *
2410
- * @param path -
2411
- * @param value -
2412
- * @returns
2599
+ * @param path - A JSONPath query to parse and evaluate against _value_.
2600
+ * @param value - Data to which _path_ will be applied.
2601
+ * @returns The {@link JSONPathNodeList} resulting from applying _path_
2602
+ * to _value_.
2413
2603
  */
2414
2604
  query(path, value) {
2415
2605
  return this.compile(path).query(value);
2416
2606
  }
2417
2607
 
2608
+ /**
2609
+ * A lazy version of {@link query} which is faster and more memory
2610
+ * efficient when querying some large datasets.
2611
+ *
2612
+ * @param path - A JSONPath query to parse and evaluate against _value_.
2613
+ * @param value - Data to which _path_ will be applied.
2614
+ * @returns A sequence of {@link JSONPathNode} objects resulting from
2615
+ * applying _path_ to _value_.
2616
+ */
2617
+ lazyQuery(path, value) {
2618
+ return this.compile(path).lazyQuery(value);
2619
+ }
2620
+
2418
2621
  /**
2419
2622
  * Return a {@link JSONPathNode} instance for the first object found in
2420
2623
  * _value_ matching _path_.
@@ -2427,6 +2630,11 @@ class JSONPathEnvironment {
2427
2630
  match(path, value) {
2428
2631
  return this.compile(path).match(value);
2429
2632
  }
2633
+
2634
+ /**
2635
+ * A hook for setting up the function register. You are encouraged to
2636
+ * override this method in classes extending `JSONPathEnvironment`.
2637
+ */
2430
2638
  setupFilterFunctions() {
2431
2639
  this.functionRegister.set("count", new Count());
2432
2640
  this.functionRegister.set("length", new Length());
@@ -2436,9 +2644,18 @@ class JSONPathEnvironment {
2436
2644
  }
2437
2645
 
2438
2646
  /**
2647
+ * Check the well-typedness of a function's arguments at compile-time.
2648
+ *
2649
+ * This method is called by the {@link Parser} when parsing function calls.
2650
+ * It is expected to throw a {@link JSONPathTypeError} if the function's
2651
+ * parameters are not well-typed.
2652
+ *
2653
+ * Override this if you want to deviate from the JSONPath Spec's function
2654
+ * extension type system.
2439
2655
  *
2440
- * @param token -
2441
- * @param args -
2656
+ * @param token - The {@link Token} starting the function call. `Token.value`
2657
+ * will contain the name of the function.
2658
+ * @param args - One {@link FilterExpression} for each argument.
2442
2659
  */
2443
2660
  // eslint-disable-next-line sonarjs/cognitive-complexity
2444
2661
  checkWellTypedness(token, args) {
@@ -2505,6 +2722,27 @@ function query(path, value) {
2505
2722
  return DEFAULT_ENVIRONMENT.query(path, value);
2506
2723
  }
2507
2724
 
2725
+ /**
2726
+ * Lazily query JSON value _value_ with JSONPath expression _path_.
2727
+ * Lazy queries can be faster and more memory efficient when querying
2728
+ * large datasets, especially when using recursive decent selectors.
2729
+ *
2730
+ * @param path - A JSONPath expression/query.
2731
+ * @param value - The JSON-like value the JSONPath query is applied to.
2732
+ * @returns A sequence of {@link JSONPathNode} objects resulting from
2733
+ * applying _path_ to _value_.
2734
+ *
2735
+ * @throws {@link JSONPathSyntaxError}
2736
+ * If the path does not conform to standard syntax.
2737
+ *
2738
+ * @throws {@link JSONPathTypeError}
2739
+ * If filter function arguments are invalid, or filter expression are
2740
+ * used in an invalid way.
2741
+ */
2742
+ function lazyQuery(path, value) {
2743
+ return DEFAULT_ENVIRONMENT.lazyQuery(path, value);
2744
+ }
2745
+
2508
2746
  /**
2509
2747
  * Compile JSONPath _path_ for later use.
2510
2748
  * @param path - A JSONPath expression/query.
@@ -2554,6 +2792,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
2554
2792
  compile: compile,
2555
2793
  expressions: expression,
2556
2794
  functions: index$2,
2795
+ lazyQuery: lazyQuery,
2557
2796
  match: match,
2558
2797
  query: query,
2559
2798
  selectors: selectors
@@ -3039,7 +3278,7 @@ var index = /*#__PURE__*/Object.freeze({
3039
3278
  apply: apply
3040
3279
  });
3041
3280
 
3042
- const version = "0.2.0";
3281
+ const version = "0.3.1";
3043
3282
 
3044
3283
  exports.DEFAULT_ENVIRONMENT = DEFAULT_ENVIRONMENT;
3045
3284
  exports.FunctionExpressionType = FunctionExpressionType;
@@ -3067,6 +3306,7 @@ exports.compile = compile;
3067
3306
  exports.jsonpatch = index;
3068
3307
  exports.jsonpath = index$1;
3069
3308
  exports.jsonpointer = index$3;
3309
+ exports.lazyQuery = lazyQuery;
3070
3310
  exports.query = query;
3071
3311
  exports.resolve = resolve;
3072
3312
  exports.version = version;